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
23 changes: 20 additions & 3 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.0.1"
author: unitoneai
license: MIT
allowed-tools: Read, Grep, Glob
Expand Down Expand Up @@ -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 |

Expand All @@ -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

Expand Down Expand Up @@ -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 |

Expand Down Expand Up @@ -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
Expand All @@ -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/
Expand All @@ -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
55 changes: 54 additions & 1 deletion skills/appsec/api-security/api-top10-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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`
Expand All @@ -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.
Expand Down