Skill Being Reviewed
Skill name: api-security
Skill path: skills/appsec/api-security/
False Positive Analysis
Benign code that could be over-flagged by the current pagination guidance:
@app.route("/api/v1/transactions")
@require_auth
def list_transactions():
cursor = request.args.get("cursor")
claims = verify_cursor(cursor, audience="transactions:list") if cursor else None
page_size = 50
if claims and claims["tenant_id"] != current_user.tenant_id:
abort(403)
rows = (
Transaction.query
.filter(Transaction.tenant_id == current_user.tenant_id)
.filter(Transaction.created_at < claims["last_seen_at"] if claims else True)
.order_by(Transaction.created_at.desc(), Transaction.id.desc())
.limit(page_size + 1)
.all()
)
return jsonify({
"items": [row.to_dict() for row in rows[:page_size]],
"next_cursor": sign_cursor({
"tenant_id": current_user.tenant_id,
"last_seen_at": rows[-1].created_at.isoformat(),
"last_id": rows[-1].id,
"aud": "transactions:list",
"exp": int(time.time()) + 900,
}) if len(rows) > page_size else None,
})
Why this is a false positive:
The existing API4 guidance says to enforce a maximum pagination size, which is correct for limit-based pagination. A reviewer can still over-report an endpoint as missing a client-visible max limit when it intentionally uses server-fixed cursor pagination. The safe property is not that the client can see a numeric limit. The safe property is that page size is enforced server-side and the cursor is opaque, signed, tenant-bound, audience-bound, expiry-bound, and tied to a stable sort key.
Coverage Gaps
Missed variant 1: client-controlled cursor payload changes tenant or filter scope
@app.route("/api/v1/accounts")
@require_auth
def list_accounts():
cursor = json.loads(base64.urlsafe_b64decode(request.args["cursor"]))
tenant_id = cursor.get("tenant_id", current_user.tenant_id)
after_id = cursor.get("after_id")
return jsonify([
a.to_dict()
for a in Account.query
.filter(Account.tenant_id == tenant_id)
.filter(Account.id > after_id)
.order_by(Account.id.asc())
.limit(100)
.all()
])
Why it should be caught:
This is both API1/BOLA and API4 resource-control risk. The endpoint has a numeric limit, so the current API4 checklist can pass it, but the cursor is user-controlled authorization state. A user can alter tenant_id, after_id, sort, or filter fields inside the cursor and traverse data outside the intended relationship boundary. The skill should require evidence that cursors are generated by the server, integrity-protected, and revalidated against the authenticated principal on every request.
Missed variant 2: unstable cursor ordering causes duplicates, skips, and scan amplification
app.get("/api/v1/audit-events", requireAuth, async (req, res) => {
const cursor = JSON.parse(Buffer.from(req.query.cursor || "{}", "base64url"));
const rows = await db.auditEvents.findMany({
where: {
tenantId: req.user.tenantId,
createdAt: { lt: cursor.createdAt || new Date() }
},
orderBy: { createdAt: "desc" },
take: 100
});
res.json({
items: rows,
next_cursor: Buffer.from(JSON.stringify({
createdAt: rows.at(-1)?.createdAt
})).toString("base64url")
});
});
Why it should be caught:
The page size is capped, but the cursor is not tied to a unique stable sort tuple such as (created_at, id), and it is not bound to a consistent snapshot. Concurrent inserts with identical timestamps can create duplicate or skipped records. Attackers can replay or manipulate cursors to force repeated expensive scans, enumerate event density, or bypass completeness assumptions in audit/export workflows. The current checklist covers maximum page size, but not cursor stability, replay, expiry, or snapshot-consistency evidence.
Edge Cases
- Opaque cursors that are encrypted but not authenticated can still be malleable depending on the mode and implementation.
- Signed cursors can still be unsafe if they omit tenant ID, user ID, query shape, sort order, audience, expiry, or page-size policy version.
- Cursor reuse across endpoints can leak data if a cursor issued for a low-sensitivity list endpoint is accepted by a high-sensitivity endpoint.
- Admin or support endpoints need explicit actor-scope fields because
tenant_id == current_user.tenant_id is not sufficient for delegated access.
- GraphQL Relay-style cursors need the same integrity and scope checks; base64 encoding a database ID is not an authorization boundary.
Remediation Quality
Recommended gates:
- API-CURSOR-01: Cursor integrity and scope gate. Require cursors to be server-issued, integrity-protected, tenant/principal-bound, audience-bound to one endpoint/query shape, expiry-bound, and revalidated against current authorization on every request.
- API-CURSOR-02: Stable ordering and snapshot gate. Require a deterministic unique sort tuple and document whether the list is snapshot-consistent, high-watermark based, or explicitly eventually consistent.
- API-CURSOR-03: Replay and cost-control gate. Require fixed server-side page size, bounded cursor lifetime, safe behavior for replayed/stale cursors, and monitoring for repeated cursor scans or query-shape abuse.
Comparison to Other Tools
| Tool |
Catches this? |
Notes |
| Semgrep |
Partial |
Custom rules can catch obvious base64/JSON cursor trust or missing signature helpers, but they usually cannot prove tenant binding or stable snapshot semantics without project-specific knowledge. |
| CodeQL |
Partial |
Dataflow can detect user-controlled cursor fields reaching queries, but query-shape binding, cursor audience, and snapshot semantics usually need manual review. |
| DAST/ZAP |
No/Partial |
DAST may detect tampered cursor responses if test users and datasets are configured, but it rarely proves stable ordering or replay/cost behavior. |
Overall Assessment
Strengths:
The api-security skill gives a strong OWASP API Top 10 structure. It correctly covers BOLA, GraphQL authorization, list endpoint filtering, and maximum pagination size.
Needs improvement:
The current guidance treats pagination mostly as a resource-consumption limit. Cursor pagination also carries authorization and consistency state. A capped page size is not enough when the cursor itself can alter tenant scope, query filters, sorting, or scan position.
Priority recommendations:
- Add cursor-specific evidence gates under API1 and API4.
- Add examples showing a safe signed, tenant-bound cursor and an unsafe decoded cursor trusted as query state.
- Add checklist items for stable sort tuples, cursor expiry, endpoint audience, replay behavior, and snapshot/high-watermark semantics.
Bounty Info
Skill Being Reviewed
Skill name: api-security
Skill path:
skills/appsec/api-security/False Positive Analysis
Benign code that could be over-flagged by the current pagination guidance:
Why this is a false positive:
The existing API4 guidance says to enforce a maximum pagination size, which is correct for
limit-based pagination. A reviewer can still over-report an endpoint as missing a client-visible maxlimitwhen it intentionally uses server-fixed cursor pagination. The safe property is not that the client can see a numeric limit. The safe property is that page size is enforced server-side and the cursor is opaque, signed, tenant-bound, audience-bound, expiry-bound, and tied to a stable sort key.Coverage Gaps
Missed variant 1: client-controlled cursor payload changes tenant or filter scope
Why it should be caught:
This is both API1/BOLA and API4 resource-control risk. The endpoint has a numeric limit, so the current API4 checklist can pass it, but the cursor is user-controlled authorization state. A user can alter
tenant_id,after_id,sort, or filter fields inside the cursor and traverse data outside the intended relationship boundary. The skill should require evidence that cursors are generated by the server, integrity-protected, and revalidated against the authenticated principal on every request.Missed variant 2: unstable cursor ordering causes duplicates, skips, and scan amplification
Why it should be caught:
The page size is capped, but the cursor is not tied to a unique stable sort tuple such as
(created_at, id), and it is not bound to a consistent snapshot. Concurrent inserts with identical timestamps can create duplicate or skipped records. Attackers can replay or manipulate cursors to force repeated expensive scans, enumerate event density, or bypass completeness assumptions in audit/export workflows. The current checklist covers maximum page size, but not cursor stability, replay, expiry, or snapshot-consistency evidence.Edge Cases
tenant_id == current_user.tenant_idis not sufficient for delegated access.Remediation Quality
Recommended gates:
Comparison to Other Tools
Overall Assessment
Strengths:
The
api-securityskill gives a strong OWASP API Top 10 structure. It correctly covers BOLA, GraphQL authorization, list endpoint filtering, and maximum pagination size.Needs improvement:
The current guidance treats pagination mostly as a resource-consumption limit. Cursor pagination also carries authorization and consistency state. A capped page size is not enough when the cursor itself can alter tenant scope, query filters, sorting, or scan position.
Priority recommendations:
Bounty Info