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
65 changes: 59 additions & 6 deletions skills/appsec/api-security/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ phase: [design, build, review]
frameworks: [OWASP-API-Security-2023, OWASP-ASVS]
difficulty: intermediate
time_estimate: "20-40min"
version: "1.0.0"
version: "1.1.0"
author: unitoneai
license: MIT
allowed-tools: Read, Grep, Glob
Expand All @@ -38,8 +38,51 @@ Before analyzing any endpoint, establish a complete inventory of the API surface
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.
8. **Capture effective method and route handling** -- Document gateway rewrites, method override support, path normalization, and whether the authorization layer evaluates the same method/path that the application executes.

> **Gate:** Do not proceed until the API style, authentication model, authorization model, and endpoint inventory are documented. Incomplete scope leads to missed findings.
> **Gate:** Do not proceed until the API style, authentication model, authorization model, endpoint inventory, and effective gateway/application method-path evidence are documented. Incomplete scope leads to missed findings.

---

## Step 1A: Effective Method and Route Normalization Evidence

Before assigning API1/API5/API8/API9 findings, determine the method and path observed at each layer: client request, CDN or gateway, reverse proxy, service mesh, framework router, authorization middleware, and handler. The review is incomplete when it records only the documented OpenAPI path but not the effective method/path after rewrites and normalization.

**Discovery patterns:**

```
# Method override and compatibility middleware
X-HTTP-Method-Override|HTTP-Method-Override|_method|methodOverride|UseHttpMethodOverride|HiddenHttpMethodFilter

# Gateway, proxy, and service-mesh route transforms
proxy_pass|rewrite|PathPrefix|StripPrefix|ReplacePath|route_rules|request_transform|uriRegexRewrite

# Framework path normalization and raw target access
AllowEncodedSlashes|decodeURIComponent|unquote|RawTarget|OriginalPath|Request.Path|PathString|UsePathBase
```

**Evidence table to build:**

| Evidence area | Required evidence | OWASP API mapping | Finding if missing |
|---|---|---|---|
| Effective method | Whether override headers or `_method` parameters are disabled, scoped, or authorized as the final method | API5:2023 / API8:2023 | A low-privilege `POST` can become privileged `DELETE`, `PUT`, or `PATCH` after authorization |
| Effective path | Gateway-visible path, forwarded path, framework route, and handler name for sensitive endpoints | API1:2023 / API5:2023 / API9:2023 | Policy authorizes one route while the application executes another |
| Normalization order | Encoded slash, duplicate slash, trailing slash, semicolon parameter, case sensitivity, and percent-decoding behavior | API1:2023 / API5:2023 | Route-level authorization can be bypassed by parser disagreement |
| Unsupported methods | Evidence that unsupported methods are rejected with `405 Method Not Allowed` at the effective routing layer | API8:2023 | Extra verbs remain reachable outside the published contract |
| Specification drift | OpenAPI methods/paths compared with gateway and code routes after rewrites | API9:2023 | Shadow operations exist only in code or gateway config |

**False positive and Not Evaluable rules:**

- Do not flag an intentional rewrite when both the rewrite table and the application handler enforce the same object/function authorization policy.
- Treat method override as acceptable only when it is disabled in production or explicitly scoped, logged, and authorized as the effective method.
- Mark normalization behavior `Not Evaluable` when only the OpenAPI document is available and proxy/framework configuration cannot be inspected; request a route table, gateway export, or controlled request evidence.
- Do not report duplicate or trailing slash handling as a vulnerability unless it changes authorization, handler selection, or documented inventory.

**Severity guidance:**

- High: method override or route rewrite reaches privileged operations without effective-method authorization, or encoded slash/semicolon handling bypasses route policy.
- Medium: OpenAPI inventory omits accepted methods or rewritten paths, or unsupported methods do not consistently return `405 Method Not Allowed`.
- Low/Informational: normalization behavior is documented but lacks regression tests and no authorization bypass is shown.

---

Expand Down Expand Up @@ -92,7 +135,7 @@ The final review output must be structured as follows:
**API Style:** [REST / GraphQL / gRPC / Hybrid]
**Specification:** [OpenAPI spec path, if applicable]
**Date:** [review date]
**Reviewer:** AI Agent -- api-security skill v1.0.0
**Reviewer:** [reviewer name] -- api-security skill v1.1.0

### Summary

Expand Down Expand Up @@ -129,6 +172,12 @@ The final review output must be structured as follows:
- **Status:** Open

[Repeat for each finding]

### Effective Method and Route Evidence

| Route / operation | Gateway method/path | Application method/path | Override/normalization behavior | Authorization point | Result |
|---|---|---|---|---|---|
| [route] | [pre-rewrite method/path] | [effective method/path] | [disabled/scoped/rewrite/Not Evaluable] | [gateway/app/policy] | [pass/finding] |
```

---
Expand All @@ -141,11 +190,11 @@ The final review output must be structured as follows:
| API2:2023 | Broken Authentication | CWE-287, CWE-307 | Weak or missing authentication mechanisms |
| API3:2023 | Broken Object Property Level Authorization | CWE-213, CWE-915 | Excessive data exposure and mass assignment |
| API4:2023 | Unrestricted Resource Consumption | CWE-770, CWE-400 | Missing rate limits, pagination caps, and resource quotas |
| API5:2023 | Broken Function Level Authorization | CWE-285 | Missing role/permission checks on operations |
| API5:2023 | Broken Function Level Authorization | CWE-285 | Missing role/permission checks on effective operations |
| API6:2023 | Unrestricted Access to Sensitive Business Flows | CWE-799, CWE-837 | Automated abuse of legitimate business logic |
| API7:2023 | Server Side Request Forgery | CWE-918 | Fetching user-supplied URLs without validation |
| API8:2023 | Security Misconfiguration | CWE-16, CWE-611 | CORS, headers, TLS, error handling, XXE |
| API9:2023 | Improper Inventory Management | CWE-1059 | Shadow APIs, deprecated versions, missing documentation |
| API8:2023 | Security Misconfiguration | CWE-16, CWE-611 | CORS, headers, TLS, error handling, method allowlists, XXE |
| API9:2023 | Improper Inventory Management | CWE-1059 | Shadow APIs, rewritten routes, method drift, missing documentation |
| API10:2023 | Unsafe Consumption of APIs | CWE-20, CWE-295 | Trusting upstream API data without validation |

---
Expand Down Expand Up @@ -215,6 +264,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. **Authorizing the pre-rewrite request instead of the effective request.** Gateways, proxies, method-override middleware, and frameworks can transform the request before the handler executes. Authorization evidence must follow the final method and route, not only the original request line.

---

## Prompt Injection Safety Notice
Expand All @@ -238,4 +289,6 @@ This skill is hardened against prompt injection. When reviewing API code and spe
- **OWASP REST Security Cheat Sheet:** https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html
- **OWASP GraphQL Cheat Sheet:** https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html
- **OWASP Testing Guide -- API Testing:** https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/12-API_Testing/
- **OWASP API5:2023 Broken Function Level Authorization:** https://owasp.org/API-Security/editions/2023/en/0xa5-broken-function-level-authorization/
- **RFC 9110 HTTP Semantics:** https://www.rfc-editor.org/rfc/rfc9110
- **NIST SP 800-204 -- Security Strategies for Microservices-based Application Systems:** https://csrc.nist.gov/publications/detail/sp/800-204/final
44 changes: 44 additions & 0 deletions skills/appsec/api-security/api-top10-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,41 @@ const resolvers = {
};
```

### Effective Method and Route Bypass Patterns

```http
POST /api/v1/users/123 HTTP/1.1
X-HTTP-Method-Override: DELETE
Authorization: Bearer regular-user-token
```

If middleware converts this request to `DELETE` after a gateway or controller has authorized it as `POST`, the review should treat the effective method as the security decision point.

```nginx
location /api/public/ {
auth_request /auth/public;
proxy_pass http://app/internal/;
}
```

If the gateway authorizes `/api/public/*` but the application receives `/internal/*`, the review must verify application-layer authorization on the effective route.

```http
GET /api/tenants/acme%2fadmin/users HTTP/1.1
GET /api//admin/users HTTP/1.1
GET /api/users;role=admin/123 HTTP/1.1
```

Encoded slash, duplicate slash, semicolon parameters, case folding, and percent-decoding order can select different handlers across proxies and frameworks. Flag this when the parser disagreement changes route policy, object scope, or handler selection.

### Remediation Guidance

- Implement a centralized authorization middleware or policy engine that enforces role/permission checks consistently across all endpoints.
- Deny by default: every endpoint should require explicit permission grants. Do not rely on "security through obscurity" of admin URL paths.
- Enforce authorization on every HTTP method independently. A user authorized to `GET` a resource is not automatically authorized to `DELETE` it.
- Authorize the effective method and route after method override, proxy rewrite, route prefix stripping, and framework normalization have occurred.
- Disable method override middleware in production unless it is explicitly scoped to non-sensitive legacy routes and logged.
- Add integration tests through the same gateway/proxy path used in production for encoded slash, duplicate slash, semicolon, trailing slash, and unsupported method cases.
- In GraphQL, use directive-based or middleware-based authorization on mutations (`@hasRole(role: ADMIN)`).
- Regularly audit the endpoint inventory against the authorization policy matrix to detect gaps.

Expand All @@ -329,6 +359,9 @@ const resolvers = {
- [ ] All administrative and privileged endpoints enforce role-based authorization.
- [ ] Authorization middleware is centralized and applied consistently.
- [ ] Each HTTP method on each endpoint has an independent authorization check.
- [ ] Method override headers and `_method` parameters are disabled, or authorized as the final effective method.
- [ ] Gateway-visible routes, forwarded routes, and application handlers enforce the same function-level policy.
- [ ] Encoded slash, duplicate slash, semicolon, case, and percent-decoding behavior cannot bypass route policy.
- [ ] GraphQL mutations enforce role/permission checks in resolvers or directives.
- [ ] The authorization policy is deny-by-default; endpoints are inaccessible unless explicitly permitted.

Expand Down Expand Up @@ -459,6 +492,8 @@ Document doc = builder.parse(request.getInputStream());
- `Cache-Control: no-store` on sensitive responses
- Return generic error messages in production. Log detailed errors server-side with correlation IDs.
- Disable unnecessary HTTP methods. Return `405 Method Not Allowed` for unsupported methods.
- Verify `405 Method Not Allowed` at the effective application route, not only at the gateway, and confirm method override cannot re-enable blocked verbs.
- Normalize or reject ambiguous paths consistently across the gateway and framework. Prefer rejecting encoded slashes, duplicate slashes, and semicolon path parameters for sensitive routes.
- Disable XML External Entity processing: set `factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)`.
- Enforce TLS 1.2+ with strong cipher suites. Disable TLS 1.0 and 1.1.
- Automate configuration scanning in CI/CD to detect drift from security baselines.
Expand All @@ -469,6 +504,8 @@ Document doc = builder.parse(request.getInputStream());
- [ ] Security headers are present on all API responses.
- [ ] Error responses in production are generic; no stack traces, SQL queries, or internal paths.
- [ ] Only required HTTP methods are enabled per endpoint.
- [ ] Unsupported methods return `405 Method Not Allowed` after proxy rewrites and framework normalization.
- [ ] Path normalization rules are explicit and consistent across the gateway, service mesh, and application framework.
- [ ] TLS 1.2+ is enforced with strong cipher suites.
- [ ] XML parsers disable external entity processing and DTD loading.
- [ ] Default credentials are changed or removed on all infrastructure components.
Expand All @@ -485,6 +522,8 @@ Document doc = builder.parse(request.getInputStream());
- Multiple API versions running simultaneously (`/api/v1/`, `/api/v2/`, `/api/v3/`) where older versions lack security patches.
- Debug or test endpoints present in production (`/api/debug/`, `/api/test/`, `/api/internal/`, `/graphql/playground`).
- Undocumented endpoints that exist in code but are absent from the OpenAPI specification.
- Gateway rewrite targets, stripped prefixes, or internal forwarded routes that are absent from the OpenAPI specification.
- Methods accepted by code or middleware that are not declared for the OpenAPI operation.
- API endpoints exposed to the public internet that should be internal-only.
- Deprecated endpoints that remain functional after the announced retirement date.
- Different security configurations between environments (staging allows unauthenticated access, production does not, but staging is publicly accessible).
Expand All @@ -498,6 +537,8 @@ Document doc = builder.parse(request.getInputStream());
4. Flag any endpoint marked as deprecated that is still reachable.
5. Check for environment-specific routes (debug, test, internal) that should not exist in production.
6. Verify that older API versions have equivalent security controls to current versions.
7. Compare gateway and proxy rewrite tables against application handlers after prefix stripping, percent decoding, and method override handling.
8. Verify that every accepted method in code or middleware is declared in the OpenAPI operation or deliberately documented as internal.
```

### Remediation Guidance
Expand All @@ -507,6 +548,8 @@ Document doc = builder.parse(request.getInputStream());
- Remove debug, test, and playground endpoints from production builds using build-time flags or environment checks.
- Segment internal APIs from external APIs at the network level (separate API gateways, VPC isolation).
- Scan for shadow APIs by comparing routing tables against documentation on every deploy.
- Export route tables from gateway, service mesh, and application framework and diff them against the OpenAPI document during release checks.
- Document intentional rewrites with both pre-rewrite and post-rewrite paths, owner, exposure, and authorization policy.

### Review Checklist

Expand All @@ -515,6 +558,7 @@ Document doc = builder.parse(request.getInputStream());
- [ ] No debug, test, or playground endpoints are accessible in production.
- [ ] Internal APIs are not reachable from external networks.
- [ ] CI/CD pipelines validate that code routes match the API specification.
- [ ] Gateway rewrites, route prefix stripping, and accepted methods match the documented inventory.

---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"route": "/api/accounts/{id}",
"gateway": {
"methods": ["GET", "PATCH"],
"rewrite": "none",
"reject_encoded_slash": true,
"reject_duplicate_slash": true,
"reject_semicolon_parameters": true
},
"application": {
"effective_methods": ["GET", "PATCH"],
"effective_path": "/api/accounts/:id",
"method_override": "disabled",
"unsupported_method_status": 405,
"authorization_point": "after framework route match"
},
"evidence": {
"gateway_path_equals_application_path": true,
"openapi_methods_match_code": true,
"controlled_negative_cases": [
"DELETE /api/accounts/123 returns 405",
"GET /api/accounts/acme%2fadmin returns 400",
"GET /api//accounts/123 returns 400"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"feature": "legacy form method override",
"production_enabled": true,
"scope": {
"allowed_paths": ["/legacy/forms/*"],
"blocked_paths": ["/api/admin/*", "/api/users/*", "/api/accounts/*"],
"allowed_original_method": "POST",
"allowed_effective_methods": ["PUT", "PATCH"]
},
"controls": {
"authorizes_effective_method": true,
"logs_original_and_effective_method": true,
"rejects_delete_override": true,
"unsupported_method_status": 405
},
"not_a_finding_rationale": "Method override is scoped to legacy non-admin form routes and authorization evaluates the effective method."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
GET /api/tenants/acme%2fadmin/users HTTP/1.1
Host: api.example.test
Authorization: Bearer user-token

HTTP/1.1 200 OK
X-Gateway-Route: /api/tenants/{tenant}/users
X-App-Route: /api/tenants/acme/admin/users

{"risk":"gateway authorized a tenant route while the app selected an admin users handler"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const express = require("express");
const app = express();

function authorizeOriginalRequest(req, res, next) {
if (req.method === "POST" && req.path.startsWith("/api/users/")) {
return next();
}
if (req.user && req.user.role === "admin") {
return next();
}
return res.status(403).end();
}

function methodOverrideAfterAuthorization(req, res, next) {
const override = req.get("X-HTTP-Method-Override");
if (override) {
req.method = override.toUpperCase();
}
next();
}

app.use(authorizeOriginalRequest);
app.use(methodOverrideAfterAuthorization);

app.delete("/api/users/:id", (req, res) => {
res.json({ deleted: req.params.id });
});

module.exports = { app, authorizeOriginalRequest, methodOverrideAfterAuthorization };
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
openapi: 3.1.0
info:
title: Method Drift Example
version: 1.0.0
paths:
/api/reports/{id}:
get:
operationId: getReport
responses:
"200":
description: Report returned
x-gateway-routes:
- path: /api/reports/{id}
accepted_methods: ["GET", "POST", "DELETE"]
forwards_to: /internal/reports/{id}
authorization_policy: documented-get-only
x-risk:
description: Gateway accepts POST and DELETE methods that are absent from the OpenAPI operation inventory.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
location /api/public/ {
auth_request /auth/public;
proxy_set_header X-Original-URI $request_uri;
proxy_pass http://app.internal/internal/;
}

location /auth/public {
proxy_pass http://policy.internal/check-public-route;
}