Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions skills/appsec/api-security/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,37 @@ For detailed checklist items with vulnerable code patterns, remediation examples

---

## Cross-Cutting Gate: Idempotency and Replay Evidence

For state-changing operations, verify that retries, duplicate delivery, and concurrent requests cannot produce duplicate side effects. This gate applies to REST create/update/delete endpoints, GraphQL mutations, webhooks, async event consumers, queue workers, and job enqueue APIs.

**Evidence to collect:**

- Inventory of high-impact state-changing operations, including charges, transfers, approvals, deletes, restores, workflow transitions, inventory changes, quota changes, webhook handlers, job producers, and GraphQL mutations.
- Idempotency key, event ID, nonce, timestamp/signature, version check, or equivalent duplicate-control requirement for each high-impact operation.
- Binding evidence showing replay controls are tied to actor, tenant, operation, resource, and payload hash.
- Atomic duplicate-detection evidence across replicas, queues, retries, and failover paths, such as unique constraints, durable ledgers, compare-and-swap, or transactional outbox records.
- Retry response behavior showing the original result, conflict, or replay rejection instead of a second side effect.
- Replay-window evidence for webhook signatures, nonces, timestamps, and event IDs.
- Logging and alerting evidence for duplicate rejects, replay rejects, retry storms, and concurrency conflicts.

**What to flag:**

```
API-REPLAY-01: State-changing operation lacks idempotency key, event ID, nonce, version check, or equivalent duplicate control
API-REPLAY-02: Idempotency key or nonce is not bound to actor, tenant, operation, resource, and payload hash
API-REPLAY-03: Duplicate detection is non-atomic across replicas, queues, retries, or failover paths
API-REPLAY-04: Retry returns a second side effect instead of original result, conflict, or replay rejection
API-REPLAY-05: Webhook or async event handler accepts duplicate event IDs without durable replay tracking
API-REPLAY-06: Replay window for signatures, timestamps, or nonces is missing or too broad
API-REPLAY-07: Balance, inventory, quota, approval, or uniqueness-sensitive operation lacks concurrency evidence
API-REPLAY-08: Duplicate/replay rejects and retry storms are not logged or alerted
```

Map these findings primarily to **API6:2023 -- Unrestricted Access to Sensitive Business Flows** for repeated business actions, and to **API4:2023 -- Unrestricted Resource Consumption** when retries or redelivery create resource exhaustion. Escalate to **High** when duplicate side effects can create charges, transfers, approvals, inventory loss, destructive deletes, or privilege changes.

---

## Findings Classification

Each finding produced by this review must include the following fields:
Expand Down Expand Up @@ -112,6 +143,12 @@ The final review output must be structured as follows:
**Total Findings:** [count]
**Critical:** [count] | **High:** [count] | **Medium:** [count] | **Low:** [count] | **Info:** [count]

### Idempotency and Replay Control Matrix

| Operation | API Style | Side Effect | Replay Control | Binding | Atomicity Evidence | Retry Response | Replay Window | Logging/Alerting |
|---|---|---|---|---|---|---|---|---|
| [POST /payments] | [REST/GraphQL/Webhook/Queue] | [charge/approval/delete/job] | [key/event/nonce/version] | [actor/tenant/resource/payload] | [unique constraint/ledger/CAS] | [original/conflict/reject] | [duration] | [signals] |

### Findings

#### API-SEC-001: [Title]
Expand Down Expand Up @@ -215,6 +252,8 @@ Unlike REST, where authorization can be enforced per endpoint, GraphQL requires

6. **Ignoring upstream API trust.** Data received from third-party APIs and even internal microservices must be validated before use. A compromised upstream service can inject SQL, XSS, or SSRF payloads through otherwise trusted data channels.

7. **Treating retries as harmless.** Client retries, mobile double-taps, webhook redelivery, and queue redelivery can repeat the same business action unless state-changing operations have durable idempotency or replay controls.

---

## Prompt Injection Safety Notice
Expand Down
65 changes: 65 additions & 0 deletions skills/appsec/api-security/api-top10-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,71 @@ def purchase_ticket():
- [ ] High-value operations require step-up verification.
- [ ] Business logic abuse scenarios are documented and monitored.

### Idempotency and Replay Evidence Gate

Rate limits reduce volume, but they do not prove that retries, duplicate webhook
delivery, queue redelivery, mobile double-taps, or concurrent duplicate requests
cannot repeat the same business action.

**Operations to inventory:**

- Payment, transfer, refund, checkout, subscription, and billing endpoints.
- Approval, workflow transition, delete, restore, and privileged action endpoints.
- Webhook handlers and async event consumers that mutate state.
- Queue producers and job enqueue endpoints.
- GraphQL mutations with financial, inventory, approval, quota, or uniqueness impact.

**Vulnerable examples:**

```python
# VULNERABLE: retrying the same request can create two charges.
@app.route("/api/v1/payments", methods=["POST"])
@require_auth
def create_payment():
charge = payment_gateway.charge(current_user.id, request.json["amount"])
Payment.create(user_id=current_user.id, gateway_id=charge.id)
return jsonify({"payment_id": charge.id}), 201
```

```javascript
// VULNERABLE: duplicate webhook event IDs are not stored or rejected.
app.post("/webhooks/provider", async (req, res) => {
await fulfillOrder(req.body.order_id);
res.sendStatus(204);
});
```

```graphql
# VULNERABLE: mutation has no idempotency key, nonce, or version check.
mutation {
approveInvoice(invoiceId: "inv_123")
}
```

**Evidence to require:**

- [ ] High-impact state-changing operations require an idempotency key, event ID, nonce, version check, or equivalent duplicate control.
- [ ] Replay controls are bound to actor, tenant, operation, resource, and payload hash.
- [ ] Duplicate detection is atomic and durable across replicas, queues, retries, and failover paths.
- [ ] Retrying the same valid request returns the original result or a conflict, not a second side effect.
- [ ] Webhook/event IDs are stored with a replay window and rejected when reused.
- [ ] Nonces, timestamps, and signatures have bounded replay windows.
- [ ] Balance, inventory, quota, approval, and uniqueness-sensitive operations include concurrency tests or transaction evidence.
- [ ] Duplicate/replay rejects and retry storms are logged and alerted.

**Finding IDs:**

| ID | Finding |
|---|---|
| API-REPLAY-01 | State-changing operation lacks idempotency key, event ID, nonce, version check, or equivalent duplicate control |
| API-REPLAY-02 | Idempotency key or nonce is not bound to actor, tenant, operation, resource, and payload hash |
| API-REPLAY-03 | Duplicate detection is non-atomic across replicas, queues, retries, or failover paths |
| API-REPLAY-04 | Retry returns a second side effect instead of original result, conflict, or replay rejection |
| API-REPLAY-05 | Webhook or async event handler accepts duplicate event IDs without durable replay tracking |
| API-REPLAY-06 | Replay window for signatures, timestamps, or nonces is missing or too broad |
| API-REPLAY-07 | Balance, inventory, quota, approval, or uniqueness-sensitive operation lacks concurrency evidence |
| API-REPLAY-08 | Duplicate/replay rejects and retry storms are not logged or alerted |

---

## API7:2023 -- Server Side Request Forgery (SSRF)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Benign: Idempotent Payment and Webhook Controls

## Review Target

```yaml
api:
style: REST + GraphQL + Webhook
state_changing_operations:
- operation: POST /api/v1/payments
side_effect: create_charge
idempotency_key_required: true
idempotency_key_header: Idempotency-Key
binding:
actor: user_123
tenant: tenant_a
operation: create_payment
resource: cart_456
payload_hash: sha256:7d9c
duplicate_detection:
storage: payments_idempotency_ledger
unique_constraint: tenant_id + actor_id + operation + key
atomic: true
cross_replica: true
failover_safe: true
retry_response: original_result
replay_window: "24h"
concurrency_test: "tests/payments/test_idempotent_double_submit.py"
logging:
duplicate_rejects: true
retry_storm_alert: true
- operation: mutation approveInvoice
side_effect: approval_transition
nonce_required: true
version_check: invoice_version_compare_and_swap
binding:
actor: approver_id
tenant: tenant_id
operation: approve_invoice
resource: invoice_id
payload_hash: mutation_hash
retry_response: conflict_on_version_mismatch
concurrency_test: "tests/graphql/test_approve_invoice_race.js"
- operation: POST /webhooks/provider
side_effect: fulfill_order
provider_event_id_stored: true
event_id_store: webhook_event_ledger
signature_timestamp_window: "5m"
duplicate_event_behavior: return_204_noop
queue_redelivery_dedup: true
logging:
replay_rejects: true

observed_evidence:
mobile_double_tap:
requests: 2
charges_created: 1
second_response: original_result
webhook_redelivery:
event_id: evt_999
deliveries: 3
fulfillments_created: 1
duplicate_responses: noop
```

## Expected Review Result

| Gate | Status | Evidence |
|------|--------|----------|
| Operation inventory | Pass | Payment, GraphQL approval, and webhook operations are inventoried. |
| Replay control | Pass | Payment uses idempotency key, GraphQL uses nonce/version check, webhook uses provider event ID. |
| Binding | Pass | Controls bind actor, tenant, operation, resource, and payload hash. |
| Atomicity | Pass | Payment ledger has unique constraint and is replica/failover safe. |
| Retry behavior | Pass | Payment retry returns original result; GraphQL conflict blocks stale approval. |
| Replay window | Pass | Payment window is documented and webhook timestamp window is five minutes. |
| Concurrency evidence | Pass | Payment and GraphQL race tests are referenced. |
| Logging and alerting | Pass | Duplicate rejects, replay rejects, and retry storms are logged or alerted. |

## Reviewer Notes

This evidence supports marking the idempotency/replay gate as controlled. Continue monitoring retry storm alerts and ensure idempotency keys are not reused across actors, tenants, operations, resources, or payloads.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Vulnerable: Duplicate Payment and Webhook Replay

## Review Target

```yaml
api:
style: REST + Webhook
state_changing_operations:
- operation: POST /api/v1/payments
side_effect: create_charge
idempotency_key_required: false
event_id_required: false
nonce_required: false
binding:
actor: false
tenant: false
operation: false
resource: false
payload_hash: false
duplicate_detection:
storage: none
atomic: false
cross_replica: false
failover_safe: false
retry_response: creates_new_charge
replay_window: none
concurrency_test: missing
logging:
duplicate_rejects: false
retry_storm_alert: false
- operation: POST /webhooks/provider
side_effect: fulfill_order
provider_event_id_stored: false
signature_timestamp_window: "24h"
duplicate_event_behavior: fulfill_again
queue_redelivery_dedup: false
logging:
replay_rejects: false

observed_evidence:
mobile_double_tap:
requests: 2
charges_created: 2
same_user: user_123
same_cart: cart_456
webhook_redelivery:
event_id: evt_999
deliveries: 3
fulfillments_created: 3
```

## Expected Findings

| ID | Severity | Evidence |
|----|----------|----------|
| API-REPLAY-01 | High | Payment operation lacks idempotency key, event ID, nonce, version check, or equivalent duplicate control. |
| API-REPLAY-02 | High | No replay control is bound to actor, tenant, operation, resource, or payload hash. |
| API-REPLAY-03 | High | Duplicate detection has no durable atomic storage and is not safe across replicas or failover. |
| API-REPLAY-04 | High | Retrying the payment creates a second charge instead of original result, conflict, or reject. |
| API-REPLAY-05 | High | Webhook handler accepts the same provider event ID and fulfills the order repeatedly. |
| API-REPLAY-06 | Medium | Signature timestamp replay window is 24 hours with no event-ID deduplication. |
| API-REPLAY-07 | High | Payment and fulfillment paths lack concurrency or transaction evidence. |
| API-REPLAY-08 | Medium | Duplicate/replay rejects and retry storms are not logged or alerted. |

## Reviewer Notes

This should be reported under API6 for duplicate business actions and API4 where redelivery can exhaust downstream resources. Require a durable idempotency ledger, event-ID store, actor/tenant/payload binding, bounded replay windows, and retry-safe responses.