diff --git a/skills/appsec/api-security/SKILL.md b/skills/appsec/api-security/SKILL.md index cbb125aa..76f44698 100644 --- a/skills/appsec/api-security/SKILL.md +++ b/skills/appsec/api-security/SKILL.md @@ -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.0.1" author: unitoneai license: MIT allowed-tools: Read, Grep, Glob @@ -66,6 +66,7 @@ Each finding produced by this review must include the following fields: | **Location** | File path and line number(s), or OpenAPI spec path | | **Description** | What the vulnerability is and why it matters | | **Evidence** | Relevant code snippet or spec excerpt demonstrating the issue | +| **CORS/PNA Evidence** | For API8 CORS or Private Network Access findings: request `Origin`, credential use, allowlist decision source, `Access-Control-Allow-*` headers, `Vary: Origin`, preflight method/header scope, environment, and Not Applicable rationale when relevant | | **Remediation** | Specific fix with code example where possible | | **Status** | Open, Mitigated, Accepted Risk, False Positive | @@ -92,7 +93,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:** AI Agent -- api-security skill v1.0.1 ### Summary @@ -144,7 +145,7 @@ The final review output must be structured as follows: | API5:2023 | Broken Function Level Authorization | CWE-285 | Missing role/permission checks on 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 | +| API8:2023 | Security Misconfiguration | CWE-16, CWE-611, CWE-942 | CORS including null-origin/PNA handling, headers, TLS, error handling, XXE | | API9:2023 | Improper Inventory Management | CWE-1059 | Shadow APIs, deprecated versions, missing documentation | | API10:2023 | Unsafe Consumption of APIs | CWE-20, CWE-295 | Trusting upstream API data without validation | @@ -215,6 +216,10 @@ 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 all dynamic CORS reflection as equal.** Exact allowlist reflection with `Vary: Origin` and endpoint-scoped preflight methods is different from arbitrary origin reflection. Review the allowlist decision source before flagging a finding. + +8. **Missing opaque-origin and Private Network Access cases.** `Origin: null` can come from sandboxed documents or non-hierarchical schemes, and Private Network Access preflights indicate a browser path toward less-public network resources. Credentialed APIs should reject opaque origins unless a trusted-origin exception is documented, and PNA grants should require a trusted-origin gate. + --- ## Prompt Injection Safety Notice @@ -229,6 +234,15 @@ This skill is hardened against prompt injection. When reviewing API code and spe --- +## Version History + +| Version | Date | Changes | +|---|---|---| +| 1.0.1 | 2026-06-06 | Added CORS null-origin and Private Network Access evidence gates, report evidence fields, false-positive guardrails, and references. | +| 1.0.0 | Initial | Baseline OWASP API Security Top 10:2023 review workflow. | + +--- + ## References - **OWASP API Security Top 10:2023:** https://owasp.org/API-Security/editions/2023/en/0x11-t10/ @@ -238,4 +252,7 @@ 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/ +- **MDN CORS Guide:** https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS +- **MDN Access-Control-Allow-Origin:** https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin +- **WICG Private Network Access:** https://wicg.github.io/private-network-access/ - **NIST SP 800-204 -- Security Strategies for Microservices-based Application Systems:** https://csrc.nist.gov/publications/detail/sp/800-204/final diff --git a/skills/appsec/api-security/api-top10-checklist.md b/skills/appsec/api-security/api-top10-checklist.md index b6569f61..970c6dd4 100644 --- a/skills/appsec/api-security/api-top10-checklist.md +++ b/skills/appsec/api-security/api-top10-checklist.md @@ -431,6 +431,28 @@ from flask_cors import CORS CORS(app, origins="*", supports_credentials=True) # Allows any origin with credentials ``` +```http +# VULNERABLE: Credentialed API trusts opaque/null origins +Origin: null +Cookie: session=... + +HTTP/1.1 200 OK +Access-Control-Allow-Origin: null +Access-Control-Allow-Credentials: true +``` + +```http +# VULNERABLE: Private Network Access preflight is granted to an untrusted public origin +Origin: https://attacker.example +Access-Control-Request-Method: POST +Access-Control-Request-Private-Network: true + +HTTP/1.1 204 No Content +Access-Control-Allow-Origin: https://attacker.example +Access-Control-Allow-Credentials: true +Access-Control-Allow-Private-Network: true +``` + ```javascript // VULNERABLE: Verbose error messages in production app.use((err, req, res, next) => { @@ -450,9 +472,35 @@ DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(request.getInputStream()); ``` +### CORS and Private Network Access Evidence Gates + +When reviewing CORS under API8, distinguish exact allowlist reflection from arbitrary origin reflection. A response that echoes `https://app.example.com` is not automatically unsafe if the implementation proves the origin matched an explicit allowlist, returns `Vary: Origin`, and limits allowed methods/headers to the endpoint's real needs. Flag arbitrary reflection, wildcard use with credentials, or broad regex/domain suffix matching that can be controlled by an attacker. + +Collect and document these evidence points for CORS/PNA findings: + +- Request `Origin`, whether credentials are in use, and whether the endpoint returns sensitive or authenticated data. +- The allowlist source and matching rule: exact string match, environment-scoped list, trusted wildcard subdomain rule, or unsafe reflection/regex. +- `Access-Control-Allow-Origin`, `Access-Control-Allow-Credentials`, `Vary: Origin`, `Access-Control-Allow-Methods`, and `Access-Control-Allow-Headers` response values. +- Treatment of `Origin: null` and other opaque origins from sandboxed documents or non-hierarchical schemes such as `file:` and `data:`. +- Private Network Access handling: `Access-Control-Request-Private-Network` requests must only receive `Access-Control-Allow-Private-Network: true` after a trusted-origin gate. +- Environment evidence showing development origins are disabled or separately scoped in production. + +Use these classification guardrails: + +| Condition | Classification | +|---|---| +| Exact allowlist match, `Vary: Origin`, endpoint-scoped methods/headers, no credential exposure beyond intended trusted origins | Not a finding | +| Credentialed API accepts `Origin: null` or reflects arbitrary origins | Finding | +| Public unauthenticated read-only API allows broad CORS without credentials or sensitive data | Low or Informational, depending on business impact | +| PNA is irrelevant because the API has no private-network resources or browser-reachable private target path | Not Applicable with rationale | +| PNA preflight grants `Access-Control-Allow-Private-Network: true` to untrusted or reflected origins | Finding | + ### Remediation Guidance -- Configure CORS with an explicit allowlist of permitted origins. Never use `*` with `credentials: true`. +- Configure CORS with an explicit allowlist of permitted origins. Never use `*` with `credentials: true`, and never treat `Origin: null` as trusted for credentialed or sensitive APIs. +- For dynamic CORS, compare the request origin to an exact allowlist before reflecting it, return `Vary: Origin`, and scope allowed methods and headers per endpoint. +- Permit local development origins only in non-production configuration, with evidence that they are disabled or environment-scoped in production. +- For Private Network Access, only return `Access-Control-Allow-Private-Network: true` after the same trusted-origin decision used for credentialed CORS. - Set security response headers on all API responses: - `Strict-Transport-Security: max-age=31536000; includeSubDomains` - `X-Content-Type-Options: nosniff` @@ -466,6 +514,11 @@ Document doc = builder.parse(request.getInputStream()); ### Review Checklist - [ ] CORS is configured with an explicit origin allowlist; wildcard is not used with credentials. +- [ ] Dynamic origin reflection is backed by exact allowlist matching and returns `Vary: Origin`. +- [ ] Credentialed or sensitive endpoints reject `Origin: null` and other opaque origins unless a documented trusted-origin exception exists. +- [ ] Preflight `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers` are scoped to endpoint needs. +- [ ] Private Network Access preflights only return `Access-Control-Allow-Private-Network: true` for trusted origins, or are marked Not Applicable with rationale. +- [ ] Development-only origins are disabled or environment-scoped in production. - [ ] 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.