Skip to content
Closed
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
4 changes: 3 additions & 1 deletion skills/appsec/api-security/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Before analyzing any endpoint, establish a complete inventory of the API surface
1. **Identify the API style** -- REST (OpenAPI/Swagger), GraphQL, gRPC, or hybrid. Each style has distinct attack patterns.
2. **Catalog all endpoints and operations** -- For REST, list every path and HTTP method. For GraphQL, list all queries, mutations, and subscriptions.
3. **Map authentication mechanisms** -- OAuth 2.0 flows, API keys, JWTs, session cookies, mTLS, or custom tokens. Note which endpoints require authentication and which are public.
4. **Identify authorization models** -- RBAC, ABAC, ownership-based, or no authorization. Document how object-level and function-level access control decisions are made.
4. **Identify authorization models** -- RBAC, ABAC, ownership-based, or no authorization. Document how object-level and function-level access control decisions are made, including how cursor-based pagination and filter changes are re-checked against tenant/user scope.
5. **Catalog data objects** -- List the resources/entities exposed by the API and their sensitivity classification (PII, financial, internal, public).
6. **Note rate limiting and quota configurations** -- Document any existing throttling, quota, or cost-control mechanisms at the gateway or application layer.
7. **Identify downstream dependencies** -- Third-party APIs, internal microservices, or webhooks that the API consumes.
Expand Down Expand Up @@ -217,6 +217,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. **Flagging cursor pagination without proof of scope breakage.** Opaque cursors are not automatically vulnerable. Treat them as a finding only when evidence shows cross-tenant replay, stale permission reuse, or missing authorization checks on the follow-up page request.

---

## Limitations
Expand Down
39 changes: 39 additions & 0 deletions skills/appsec/api-security/api-top10-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ BOLA occurs when an API endpoint accepts an object identifier from the client an
- Authorization logic that checks only whether the user is authenticated, not whether they own or have access to the specific object.
- Sequential or predictable resource identifiers (auto-increment integers) that enable enumeration.
- Batch or list endpoints that return objects without filtering by the caller's permissions.
- Cursor-based pagination or search tokens that can be replayed across tenant, account, or filter boundaries.
- Opaque cursors that encode a sort key or record ID but omit tenant scope, permission version, or server-side revalidation on each page request.

### REST Vulnerable Patterns

Expand Down Expand Up @@ -80,6 +82,40 @@ const resolvers = {
};
```

### Cursor Pagination Patterns

```python
# VULNERABLE: Cursor advances by ID only; tenant scope is never re-checked
@app.route('/api/v1/invoices')
@require_auth
def list_invoices():
cursor = request.args.get("cursor")
query = Invoice.query.order_by(Invoice.id.asc())
if cursor:
query = query.filter(Invoice.id > decode_cursor(cursor)["last_id"])
invoices = query.limit(50).all()
return jsonify([invoice.to_dict() for invoice in invoices])
```

```python
# SECURE: Cursor stays bound to tenant scope and current authorization
@app.route('/api/v1/invoices')
@require_auth
def list_invoices():
cursor = request.args.get("cursor")
scope = {"tenant_id": current_user.tenant_id, "role": current_user.role}
query = Invoice.query.filter_by(tenant_id=current_user.tenant_id).order_by(Invoice.id.asc())
if cursor:
decoded = decode_cursor(cursor)
if decoded["tenant_id"] != current_user.tenant_id:
return jsonify({"error": "Invalid cursor"}), 400
query = query.filter(Invoice.id > decoded["last_id"])
invoices = query.limit(50).all()
return jsonify([invoice.to_dict() for invoice in invoices])
```

**Benign exception:** Do not flag cursor pagination by default when the cursor is opaque, the server binds it to tenant/user scope, and every page request re-checks authorization before returning results. The finding needs proof that a cursor from one scope can fetch data from another scope or outlive a permission change.

### BOLA vs BFLA Distinction

BOLA and BFLA (API5:2023) are frequently confused. The distinction is critical for accurate findings:
Expand All @@ -99,8 +135,11 @@ Both can coexist in a single endpoint. An endpoint may lack both a role check (B
- [ ] Every endpoint that accepts a resource identifier enforces ownership or relationship-based access control.
- [ ] Authorization checks happen at the data access layer, not only at the controller/route layer.
- [ ] Batch/list endpoints filter results by the caller's permissions.
- [ ] Cursor-based pagination and search tokens are bound to tenant/user scope and revalidated on every follow-up request.
- [ ] Resource identifiers are UUIDs or non-sequential values to resist enumeration.
- [ ] GraphQL resolvers enforce authorization on every field that returns sensitive data.
- [ ] Findings about cursor tenant isolation include concrete evidence that cross-tenant replay, stale authorization reuse, or filter-bypass is actually possible.
- [ ] Remediation includes a regression test proving a cursor from tenant A cannot fetch tenant B data after filter, role, or membership changes.

---

Expand Down
Loading