Audit REST, GraphQL, and RPC APIs against the OWASP API Security Top 10 (2023). Use when the user mentions 'API security,' 'API audit,' 'BOLA,' 'broken object level authorization,' 'BFLA,' 'function-level authorization,' 'mass assignment,' 'API rate limiting,' 'GraphQL security,' 'REST security,' 'API authentication,' 'API authorization,' 'excessive data exposure,' or needs to review API endpoints for security weaknesses.
71
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Perform a systematic security audit of API endpoints against the OWASP API Security Top 10 (2023). Distinct from owasp-audit — that's category-driven over a whole codebase, this is surface-driven over the API contract.
Use owasp-audit for the codebase as a whole. Use this when you need a focused pass over every endpoint with API-specific bypass patterns. They cross-reference each other where categories overlap.
The #1 API vulnerability by exploitation frequency. Every endpoint that accepts an object ID needs an explicit ownership check before reading or mutating.
/users/:id, /orders/:id, /projects/:id), verify a query like findFirst({ where: { id, userId } }) runs before any data access — not findById(id) then a separate checkposts.find(id).user.creditCard returns another tenant's card if the relation isn't guarded. Audit every .include, .with, includes, eager-loaded relationparams.id, req.params.<id>, formData.get("<id>"), ORM .findById( / .find( without a where clause, GraphQL resolvers that take an id arg and call .findUniquealg: none accepted by the verifiercrypto.timingSafeEqual / equivalentprocess.env.X without a presence check — when X is unset, "Bearer ${undefined}" is a valid literaljwt.verify, jsonwebtoken, Bearer ${process.env, jwt.decode (without verify), verify.*algres.json(user) leaks password_hash, stripe_customer_id, internal flagsmutation UpdateUser($id, $role) succeeds because the resolver only checks "is the user logged in"res.json(<entity>) without explicit field projection, Object.assign(record, req.body), Mongoose findByIdAndUpdate(id, req.body), Drizzle .update().set(req.body), Sequelize update(req.body), GraphQL resolvers without role checks?limit=10000000 returns 10M rowsuser { posts { user { posts { ... } } } } runs foreverPOST /api/v1/users/me to POST /api/v1/users/<other_id>DELETE /admin/users/123 blocked, but POST /admin/users/123/delete succeedsreq.user.role === "admin" where role is set from a header the client controlsPUT /:id is guarded but POST /:id/send writes the same row without the guard. Run sister-route audit (see owasp-audit)owasp-audit A10 for the full bypass matrix (9+ patterns including IPv4-mapped IPv6, trailing-dot, cloud metadata, encoded IPs)redirect: "follow" (default) on fetches with user-controlled URLs lets an attacker bounce through a 302 into the metadata serviceAccess-Control-Allow-Origin: * combined with Allow-Credentials: true (browsers refuse this; servers shouldn't ship it)Origin header without an allow-list — any origin gets allowedowasp-audit A05 for the full baseline values/v1/) still live and unpatched alongside /v2/ — attackers prefer the version with fewer checks*-staging.fly.dev left open)/__debug, /__db, /.well-known/internal/, framework-default routesapp.get, router.get, pages/api/, app/api/ directory contents; reconcile against the published API contractprovider.user.profile_url, XSS via provider.user.bio rendered un-escaped__schema, __type queries return the full schema)GET accepted on state-changing endpoints (CSRF risk + cache poisoning)application/json but accepts application/x-www-form-urlencoded and parses inconsistently/files/../../etc/passwdowasp-audit A02 type-coercion + A04 multi-tenant signature N-way matching)/webhooks/stripe) without IP allow-list or signatureOrigin: https://evil.com; observe the browser blocks, not the servertsc --noEmit + build success ≠ fix verified. See also owasp-audit's Verify Fixes at Runtime + Second-Opinion Pass — same playbook applies.
Findings have three dispositions (Fixed / Deferred / Accepted Risk) per the owasp-audit convention. For every finding:
#### [SEVERITY] APIN: [Title]
**Endpoint:** `METHOD /path/to/endpoint`
**Handler:** `path/to/handler.ts:42`
**CWE:** CWE-XXX
**Description:** [What the vulnerability is]
**Proof of concept:**[curl / request showing the bypass]
**Vulnerable Code:**
[snippet]
**Remediation:**
[fixed snippet]
**Verification:** [Concrete adversarial input and the observed response that proves the fix holds]Produce an executive summary grouped by API category, plus an inventory of every endpoint with one of:
c9ade03
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.