We Read an OpenAPI Spec and Found 13 Vulnerabilities in 2.4 Seconds
What We Built
78% of APIs have at least one OWASP API Top 10 vulnerability. The problem isn't usually that developers don't know about authentication. It's the gap between what the spec says is protected and what the server actually enforces at runtime.
We built a Flask REST API with a complete OpenAPI 3.0 specification and four vulnerabilities deliberately embedded — one per OWASP API Top 10 category:
| # | Vulnerability | Endpoint | OWASP Class |
|---|---|---|---|
| V1 | BOLA/IDOR — integer user IDs | GET /api/v2/users/{id} |
API1 — Broken Object Level Authorization |
| V2 | Unauthenticated admin endpoint | GET /api/v2/admin/users |
API2 — Broken Authentication |
| V3 | Excessive data exposure | GET /api/v2/products |
API3 — Broken Object Property Level Auth |
| V4 | Deprecated v1 API still live | GET /api/v1/users |
API9 — Improper Inventory Management |
We also left in three undocumented "shadow" endpoints — routes that exist in the running Flask app but appear nowhere in the spec. And we left Flask in debug mode with verbose error responses enabled on the login endpoint.
The API had 8 documented endpoints across v1 and v2. The scanner discovered all 8 from the spec — plus found the shadow endpoints by actively probing paths common to Python/Flask apps.
What SecurityClaw Found
Total findings: 13 — 1 Critical, 9 High, 3 Medium. Scan time: 2.46 seconds.
============================================================
Swagger/OpenAPI Security Scan
============================================================
Spec: http://localhost:5002/openapi.json
Base URL: http://localhost:5002
API: SecurityClaw Demo API (v2.0.0)
Endpoints: 8 discovered
Duration: 2.4s
Findings: 13 total | 🔴 1 Critical | 🟠 9 High | 🟡 3 Medium | 🔵 0 Low
1. 🔴 CRITICAL — Unauthenticated Access to Auth-Required Endpoint
Endpoint: GET /api/v2/admin/users
Evidence: HTTP 200 received without auth token. Response length: 283 bytes.
7. 🟠 HIGH — Integer ID Parameter - Potential BOLA/IDOR Vulnerability
Endpoint: GET /api/v2/users/{userId}
Evidence: Sequential enumeration (1, 2, 3...) may expose other users' data.
8. 🟡 MEDIUM — Verbose Error Messages / Stack Trace Exposure
Endpoint: POST /api/v2/auth/login
Evidence: HTTP 500 response contains: {"db_connection":"sqlite:///demo.db",
"debug_traceback":"Traceback (most recent call last)...","secret_key":"dev-secret-do-not-use"}
9. 🟠 HIGH — Shadow/Undocumented Endpoint: /admin
Evidence: GET http://localhost:5002/admin → HTTP 200. 220 bytes.
Summary: 1 Critical, 9 High, 3 Medium
The Critical Finding: Spec Says Auth. Server Says No.
The most important finding came first. The OpenAPI spec for GET /api/v2/admin/users clearly defines a bearerAuth security requirement with admin scope. The endpoint is documented as requiring authentication.
The server disagreed. An unauthenticated GET request returned HTTP 200 with the full user list. The scanner probed it without a token, got data, and flagged it Critical in under a second.
This is API2 (Broken Authentication) in the most direct form: the spec is documentation, not enforcement. The authentication check was either never implemented, commented out during development, or accidentally removed. The spec doesn't protect anything — the running code does.
BOLA/IDOR: Integer IDs Are an Enumeration Problem
The scanner flagged GET /api/v2/users/{userId} as a High severity BOLA risk due to the integer parameter type. Sequential user IDs (1, 2, 3...) are trivially enumerable. A scanner can enumerate them automatically; an attacker can do it manually in minutes.
This is OWASP API1 territory. The spec defines the parameter as an integer — the scanner reads that and immediately flags the enumeration risk. No authentication check required; the issue is structural. The fix involves either UUIDs or proper per-request authorisation enforcement (verify the requesting user owns the resource, regardless of what ID they provided).
BOLA remains the most exploited API vulnerability in real breaches. If you're building REST APIs and your resource IDs are integers, this is your first audit target. The Web Application Hacker's Handbook covers IDOR exploitation patterns in depth — still essential reading despite its age, particularly for understanding why access control failures are so prevalent.
Shadow Endpoints: Three Routes Nobody Documented
The scanner probed paths common to Flask applications beyond the documented spec endpoints. It found three routes returning HTTP 200 that appeared nowhere in the OpenAPI spec:
- /admin — returns database path, Redis host URL, user count, environment label
- /debug — returns Python installation paths, environment variable count, Flask debug mode status
- /metrics — full Prometheus-style traffic metrics with per-endpoint request counts
The /admin response is worth quoting directly:
{
"panel": "Admin Dashboard",
"db_path": "/var/app/demo.db",
"redis_host": "redis://localhost:6379",
"users_total": 4,
"env": "production"
}
These routes weren't in any spec, any document, any threat model. They existed in the code and responded with real infrastructure data. The scanner found them by probing — not by reading spec paths, but by actively testing candidate URLs that Flask frameworks commonly expose.
Shadow endpoints are how attackers discover attack surface that your security review missed entirely. Your threat model was built from the spec. The spec was wrong about what was exposed.
The Login Endpoint's Secret: Flask Secret Key in a 500 Response
The scanner sent a malformed request to POST /api/v2/auth/login and triggered an unhandled exception. Flask's debug mode responded with the full stack trace — plus a JSON error body that included the Flask application's secret key:
{
"error": "Internal server error",
"debug_traceback": "Traceback (most recent call last):\n File \"/app/app.py\"...",
"flask_env": "development",
"db_connection": "sqlite:///demo.db",
"secret_key": "dev-secret-do-not-use-in-prod"
}
The Flask secret key is used to sign session cookies. If an attacker has the secret key, they can forge any session cookie in the application. This isn't an API design problem — it's "someone left app.debug = True in production." The scanner found it in 2.4 seconds by doing what any competent penetration tester does on day one: send malformed input to authentication endpoints and look at error responses.
Full Finding Breakdown
| # | Severity | Finding | Check Type |
|---|---|---|---|
| 1 | 🔴 Critical | Unauthenticated access to /api/v2/admin/users (spec requires admin scope) |
UNAUTHENTICATED_ACCESS |
| 2 | 🟠 High | Write endpoint PUT /api/v2/users/{userId}/preferences — no auth definition in spec |
MISSING_AUTH_DEFINITION |
| 3 | 🟠 High | /api/v2/admin/users — sensitive/admin endpoint in public spec |
SENSITIVE_ENDPOINT_EXPOSURE |
| 4 | 🟠 High | /api/v2/debug/health-check — debug endpoint in public spec |
SENSITIVE_ENDPOINT_EXPOSURE |
| 5 | 🟠 High | /api/v2/internal/reset-demo — internal endpoint in public spec |
SENSITIVE_ENDPOINT_EXPOSURE |
| 6 | 🟡 Medium | Dev/test endpoints (/debug/..., /internal/...) in production spec |
DEV_ENDPOINTS_IN_SPEC |
| 7 | 🟠 High | GET /api/v2/users/{userId} — integer userId, enumerable BOLA risk |
BOLA_INTEGER_IDS |
| 8 | 🟡 Medium | POST /api/v2/auth/login — Python stack trace + secret_key in 500 body |
VERBOSE_ERRORS |
| 9 | 🟠 High | Shadow endpoint /admin — 200 OK, not in spec. Returns DB path and Redis host. |
SHADOW_ENDPOINTS |
| 10 | 🟠 High | Shadow endpoint /debug — 200 OK, not in spec. Returns internal platform config. |
SHADOW_ENDPOINTS |
| 11 | 🟠 High | Shadow endpoint /metrics — 200 OK, not in spec. Full traffic metrics exposed. |
SHADOW_ENDPOINTS |
| 12 | 🟠 High | /openapi.json — flagged as shadow endpoint (minor FP — it's the spec URL itself) |
SHADOW_ENDPOINTS |
| 13 | 🟡 Medium | Method override accepted: X-HTTP-Method-Override: DELETE returns 200 |
METHOD_OVERRIDE |
On finding #12 — the scanner flagged /openapi.json itself as a shadow endpoint because it's not listed as a path in the spec's paths object. Technically accurate. In practice, the spec URL is expected to be accessible. We documented this as a known false positive.
The Honest Gaps: What It Missed
We found 13 issues in 2.4 seconds. We also missed 2 of the 4 planted vulnerabilities directly. Here's exactly why.
❌ V3 — Excessive Data Exposure: Not Detected
We planted this in GET /api/v2/products. The endpoint returns internal business fields that have no business being exposed to API consumers: cost_price, internal_margin, supplier_id, warehouse_stock, internal_sku. These fields aren't in the spec's response schema.
The scanner missed it because detecting excessive data exposure requires runtime response analysis: make an authenticated request, parse the JSON body, compare each returned field name against the spec's defined properties in the response schema, flag undocumented fields.
The current scanner does structural and authentication analysis. It doesn't yet perform schema-aware response diffing. It's a meaningful gap — API3 (excessive data exposure) is a top-5 OWASP API finding in real audits, and it requires actually making requests and inspecting what comes back. The fix isn't technically complex; it's just not built yet.
The AI compensating factor: During the analysis phase, the AI layer can flag any response body containing fields matching patterns like cost_price, margin, internal_*, supplier_* as potential API3 candidates. That's a manual review step the AI analysis can handle — but it requires seeing the actual response, which means someone needs to make the authenticated call first.
❌ V4 — Deprecated API Still Live: Not Detected
The OpenAPI spec marks /api/v1/users as deprecated: true with x-sunset-date: 2025-06-01. It was supposed to be retired 9 months ago. It still returns 200 with real data.
The scanner parsed the spec, saw the deprecated annotation, and… moved on. No check that the endpoint was actually responding.
The fix is simple: for each endpoint with deprecated: true, probe it and flag if it returns 2xx instead of 410 Gone. This is a single additional check that would catch every deprecated-but-live endpoint. It's on the roadmap and should be in the next version.
Deprecated APIs that silently stay live are a widespread problem. They miss security patches. They lack monitoring. Nobody owns them. They accumulate vulnerabilities while the new API gets the attention. OWASP API9 (Improper Inventory Management) exists for exactly this reason.
The Score
2 of 4 planted vulnerabilities found directly. 11 additional real issues found as bonus findings. 1 minor FP (/openapi.json as shadow endpoint). The 2 missed checks are on the roadmap with clear implementation paths.
We found 11/13 issues in 2.4 seconds. The 2 we missed require response body analysis and deprecated-endpoint probing — both straightforward to add.
What This Means for Your API
Three takeaways that apply to any API, not just this demo.
1. Your OpenAPI spec is documentation, not enforcement
The spec said /api/v2/admin/users required admin auth. The server didn't care. This gap — between what the spec documents and what the server enforces — is where the majority of API authentication failures live. If your security review process stops at "the spec says auth is required," you're missing the actual check.
Spec-based scanning closes this gap by treating the spec as a test oracle: "the spec claims X; let's verify X is true." That verification takes 2.4 seconds. The traditional alternative is a penetration tester manually probing each endpoint — which takes days and costs significant budget. If you're building APIs professionally, The Web Application Hacker's Handbook remains the best single resource for understanding how these access control failures are found and exploited.
2. Shadow endpoints are everywhere
None of the three shadow endpoints we found were in any spec, any commit message, any design document, any threat model. They existed in code and responded to requests. Your threat model was built from documentation; your attack surface includes everything that responds to an HTTP request.
Framework-default routes (/metrics, /health, /debug, /admin) are particularly common shadow endpoints in Flask, Django, and Spring Boot applications. A scanner that only reads your spec will miss all of them. A scanner that reads your spec and probes common candidate paths will find them in seconds.
3. Debug mode in production is an instant critical
Flask's app.debug = True leaking a secret key in a 500 response is not a subtle issue. It's an immediate session forgery risk for every user on the platform. It takes one malformed login request to expose it. The scanner found it by doing exactly what any manual tester does: send bad input to auth endpoints, look at the error.
Error handling is one of the most consistently under-tested areas in API security reviews. Teams spend significant time on authentication and authorisation logic; they spend far less time systematically checking what their error responses reveal. Black Hat Python (2nd Edition) covers building custom probes for exactly this kind of verbose error hunting — useful background if you're building your own security automation.
What SecurityClaw does: The swagger-scanner is one of 56+ skills in SecurityClaw's automated penetration testing platform. It reads your OpenAPI/Swagger spec, maps your documented API surface, tests authentication enforcement, probes for shadow endpoints, and flags structural vulnerabilities in seconds — not days. Learn more about SecurityClaw →
← Back to all SecurityClaw Demos | See also: D2 Nikto (web misconfiguration) | D5 Nuclei (cloud/web misconfigs)