SAST scanner

OpenAPI Spec Scanner

An API spec is a map of every route and what it demands. This scanner reads the spec files committed in your repository and asks six questions of their text: is there a global security requirement, is every server HTTPS, is Basic auth used, is an API key put in the query string, does any operation switch security off, and do sensitive-looking paths sit in a spec with no global security.

SASTMedium to HighAPI exposurerepository onlyno LLM

Which files are specs

There is no list of file names. The scanner walks the repository's in-memory file index and keeps a file only if it passes two gates: its extension, then a marker in its text. This is the static scanner for specs in your repo; probing a live target for a served /openapi.json is a different job, done by endpoint discovery.

Repository file index openapi.yamlspec api/swagger.jsonspec package.jsonno marker docs/api.mdwrong extension k8s/config.ymlno marker 1Extension .yaml .yml .json a path ending in any of these 2Marker in the text openapi: swagger:"openapi": "swagger": regex match anywhere in the file Spec files openapi.yamlapi/swagger.json each runs all six checks an error in one file is logged; the next file still runs

1The scanner reads files already in memory from repository ingestion. It needs no network, no credentials and no LLM, and it never asks for a particular name: openapi.yaml, api/swagger.json and spec/v2.yml are treated alike.

2Gate one: extension. Only paths ending in .yaml, .yml or .json go on. A Markdown file that quotes openapi: 3.0 is dropped here.

3Gate two: marker. The text must match openapi:, swagger:, "openapi": or "swagger":. That keeps package.json and ordinary config out. What survives is the spec set, and every spec gets the six checks.

Six regex checks

Each check is a regular expression over the raw text of the file. There is no YAML or JSON parser, so there is no notion of "this operation" or "this server entry"; a pattern either appears or it does not. The sample below is built to trip all six.

openapi: 3.0.0 servers: - url: http://api.example.com paths: /payments: post: {} /health: get: security: [] components: securitySchemes: basic: type: http scheme: basic key: type: apiKey in: query (no line begins with security:) 1No global securitysecuritySchemes missing, or no line starting security:HIGH · 0.85 2Plain-HTTP serverurl: http://… unless localhost, 127.0.0.1, 0.0.0.0MEDIUM · 0.80 3Basic authtype: http, then scheme: basic within 50 charactersMEDIUM · 0.85 4API key in querytype: apiKey, then in: query within 50 charactersMEDIUM · 0.85 5Auth switched offsecurity: [] anywhere in the fileMEDIUM · 0.80 6Sensitive path7 path words; only if no top-level security: lineHIGH · 0.75

1One spec, six findings, one per check. Highlighted lines are what each regex latches onto.

2No global security. Fires when either securitySchemes (or securityDefinitions) is missing or no line starts with security: at column 0. Here the scheme is defined but never applied, so every endpoint is public by default. HIGH, 0.85.

3HTTP server. A url: that starts with http://, skipped for localhost, 127.0.0.1 and 0.0.0.0. One finding per URL: traffic to the API is unencrypted. MEDIUM, 0.80.

4Basic auth. type: http followed within 50 characters by scheme: basic. Credentials travel Base64-encoded on every request. MEDIUM, 0.85.

5API key in the query. type: apiKey followed within 50 characters by in: query. Keys in URLs end up in server logs, browser history and proxy logs. MEDIUM, 0.85.

6Security switched off. Any security: [] opts that part of the API out of authentication. One finding per occurrence; the check cannot tell which operation it belongs to. MEDIUM, 0.80.

7Sensitive path. A path key starting with /admin, /users, /payment(s), /billing, /token(s), /auth or /secret(s), reported only when the file has no top-level security: line. HIGH, 0.75.

The global-security guard

Checks 1 and 6 both hinge on one pattern, ^security\s*:, matched per line. A spec that applies a requirement at the top level silences both. How the pattern reads three kinds of file shows what it protects and where it misleads.

A · YAML, top-level security: - bearerAuth: []paths: line 1 starts with security: matches · checks 1 and 6 silent B · only one operation paths: /orders: get: security: [ { b: [] } ] indented, so not a top-level line no match · flagged HIGH C · JSON, top-level { "openapi": "3.0.0", "security": [{"b": []}], line starts with spaces and a quote no match · flagged although protected pattern, applied to each line: ^security\s*: With a match, check 1 passes (given a scheme) and check 6 never runs. Without one, both can fire.

1The pattern asks one thing: does any line begin with security:? That is a YAML idiom, and the three cases below differ in whether it holds.

2Case A. A top-level security: block in YAML is found. A requirement is applied to the whole API, so check 1 stays quiet when a scheme exists and the sensitive-path check never runs.

3Case B. security: under a single operation is indented, so it is not global. That is the right outcome: one guarded route does not make the rest of the API protected, and the spec is flagged.

4Case C. A JSON spec with a top-level "security" key does not match, because the line starts with whitespace and a quote. Such a spec is flagged as having no global security even though it has one. For JSON specs, read checks 1 and 6 as "verify by hand".

Why it matters

Why it matters

OpenAPI specs are often served at /docs, /swagger or /api-docs in production. They hand an attacker the complete API map (every endpoint, parameter and response schema), which routes need authentication and which do not, any internal admin routes, and the exact field names and types for crafting payloads.

Real-world context

Exposed Swagger or OpenAPI documentation is a common bug bounty finding. Attackers routinely check /api-docs, /swagger.json and /openapi.yaml as their first reconnaissance step, and use the spec to pick the most promising targets.

Vulnerable vs fixed

# BAD: nothing applies authentication, and the transport is plain HTTP
openapi: 3.0.0
servers:
  - url: http://api.example.com
paths:
  /payments:
    post: {}
components:
  securitySchemes:
    basic: { type: http, scheme: basic }
    key:   { type: apiKey, in: query, name: api_key }
// BAD: the interactive spec is public in production
app.use("/api-docs", swaggerUi.serve)

The static scanner reads what is committed; it cannot tell whether the UI is served. Fixing the spec text clears these six findings, and not serving the spec in production removes the reconnaissance value that makes the findings matter.

Limits

TopicBehaviour
NeedsAn indexed repository. No credentials, no LLM key, no external binary. Without a repo it does not run.
MatchingRegex over raw text. Comments and example values count as text, so a commented-out security: [] would also match.
MultiplicityOne finding for check 1 per file; checks 2, 5 and 6 report once per match (per URL, per path key, per security: []); checks 3 and 4 once per file.
CategoriesChecks 1, 3, 5 and 6 report as auth weakness; the HTTP server as mixed content; the API key in the query as exposed secrets.
OverlapA path such as /admin/users: matches both the /admin and /users patterns and is reported twice.
Not coveredExample values holding real keys or PII, and freeform object parameters, are not inspected: no check in the code covers them.