OpenAPI Security Auditor
Pricing
from $50.00 / 1,000 api auditeds
OpenAPI Security Auditor
Audit OpenAPI/Swagger documents and public API endpoints for authentication gaps, sensitive-data exposure, missing rate limits, TLS and security-header issues. Scored JSON, Markdown and SARIF output for CI/CD.
Pricing
from $50.00 / 1,000 api auditeds
Rating
0.0
(0)
Developer
Justin Hagerty
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
12 days ago
Last modified
Categories
Share
Audit OpenAPI 3.x and Swagger 2.0 documents for security issues in seconds: missing authentication, weak OAuth flows, exposed sensitive data, missing rate limits, insecure servers, deprecated patterns and specification gaps. Optional passive checks look at the live API's TLS, security headers and CORS. You get a 0–100 score plus JSON, Markdown and SARIF reports that drop straight into CI/CD and GitHub code scanning.
Give it a spec URL, a pasted document, or just an API base URL and it finds the spec for you. Every finding links to OWASP API Security Top 10 (2023) and CWE references, includes a concrete fix, and says whether it came from the specification or from a runtime observation.
What does OpenAPI Security Auditor do?
The Actor reads your API description and runs 63 deterministic rules against it:
| Area | Examples of what is checked | OWASP API Top 10 |
|---|---|---|
| Authentication & authorization | Operations without effective auth (with a BOLA hint for /{id} paths and write methods), explicit security: [] opt-outs, no security schemes at all, HTTP Basic, API keys in query strings, OAuth 2.0 implicit and password grants, missing or overly broad scopes, optional auth ({}), undefined or unused schemes, OAuth endpoints over HTTP, cookie credentials | API1, API2, API5 |
| Insecure API definitions | http:// servers, servers that point at localhost, private IPs or staging/dev hosts, unbounded strings, arrays and numbers, request objects that accept undeclared properties (mass assignment), privileged properties such as role or isAdmin that clients can write, file uploads without size limits, */* request bodies | API3, API4, API8 |
| Rate limiting | No 429 responses, no RateLimit/X-RateLimit-*/Retry-After headers, login/token/OTP endpoints without documented throttling, list endpoints without pagination | API2, API4 |
| Sensitive data exposure | Passwords, hashes and secrets in response schemas, PII and tokens in responses, sensitive values in URLs, credentials hard-coded in examples, defaults or descriptions (always redacted in the output) | API3 |
| Deprecated patterns | Swagger 2.0 documents, deprecated operations that are still exposed, several API versions (/v1, /v2) served side by side, implicit flow | API9 |
| Inventory | Admin, debug, actuator and internal endpoints in public docs | API5, API9 |
| Specification quality | Missing or duplicate operationIds, undocumented operations, no 4xx or 401/403 responses, responses without schemas, no contact or security contact, unresolvable or external $refs, structural errors, no servers, path-template mismatches | — |
| Runtime (opt-in) | Invalid or expiring TLS certificates, TLS 1.0/1.1 accepted, protocol below TLS 1.2, missing or weak HSTS, missing nosniff, Server/X-Powered-By version disclosure, JSON without Cache-Control, plain-HTTP serving, CORS wildcard with credentials, CORS reflection of arbitrary origins | API8 |
See RULES.md for the full catalog: every rule's ID, severity, category, evidence kind, recommendation and references.
Who is it for?
- API developers who want a quick security review of a spec before publishing it.
- Platform and security teams who want to check many APIs the same way and track a comparable score over time.
- CI/CD pipelines that need to fail a build when a pull request adds an unauthenticated endpoint or leaks a password field. SARIF output shows up as GitHub code-scanning alerts.
- Consultants and auditors who need a fast first pass, with references, before a manual review.
- AI agents and automations that need a cheap tool with structured output (JSON and stable rule IDs) and no subscription.
Key features
- Swagger 2.0, OpenAPI 3.0 and 3.1 in JSON or YAML, all mapped to one internal model, so every rule works the same on every version.
- Safe
$refresolution. Only local#/…pointers are followed. Resolution handles cycles and is depth-limited. Broken and external refs are reported, and remote documents are never fetched. - Discovery. Give it
https://api.example.comand it tries the base URL plus 8 well-known documentation paths (/openapi.json,/openapi.yaml,/swagger.json,/v3/api-docs,/v2/api-docs,/api-docs,/swagger/v1/swagger.json,/.well-known/openapi.json), so at most 9 requests. - Clear separation of evidence. Each finding's
kindisspecification(what the document declares),runtime(what we observed on the live host) orobservation(neutral facts). A declared issue is never presented as a confirmed vulnerability. - Passive, opt-in runtime checks. One TLS handshake, one
GETand one CORS preflight per API, and only after you confirm you are authorized. No fuzzing, no credential guessing, no exploitation. - Deterministic 0–100 score and risk level that are comparable across runs and APIs.
- Built for CI:
failOnSeveritygate, SARIF 2.1.0 (REPORT.sarif), Markdown (REPORT.md), stable rule IDs and finding fingerprints for baselines. - Secrets stay secret. Detected credentials are redacted (
AKIA…8W (20 chars)). Raw values never reach the dataset, logs or reports. - Hardened fetching. The SSRF guard blocks private, loopback and cloud-metadata addresses. Responses are size-capped and time-limited, every redirect is re-validated, and requests use an honest User-Agent.
How to use it
In Apify Console
- Open the Actor and click Try for free.
- Paste one or more spec URLs into OpenAPI / Swagger document URLs. You can also paste a document into Pasted OpenAPI / Swagger document, or list API base URLs for discovery.
- (Optional) Turn on Run passive runtime checks and tick I am authorized to test these hosts.
- Click Start. Each API takes a few seconds.
- Open the Output tab. The Overview view shows one row per API with its score. The Findings view shows one row per issue with its fix. The key-value store holds
REPORT.md,REPORT.sarifand theOUTPUTrun summary.
Via the API (curl)
This runs the Actor synchronously and returns the dataset items, meaning one report per API:
curl -s -X POST \"https://api.apify.com/v2/acts/michiganpublictech~openapi-security-auditor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"specUrls": ["https://petstore3.swagger.io/api/v3/openapi.json"],"minSeverity": "low"}' | jq '.[] | {target, score, risk_level, summary}'
Add &format=csv or use the findings view (&view=findings) for one row per finding.
From Python (apify-client)
from apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("michiganpublictech/openapi-security-auditor").call(run_input={"specUrls": ["https://petstore3.swagger.io/api/v3/openapi.json"],"apiBaseUrls": ["https://api.example.com"],"runtimeChecks": True,"confirmAuthorization": True, # only for hosts you own or may test})for report in client.dataset(run["defaultDatasetId"]).iterate_items():print(report["target"], report["score"], report["risk_level"])for f in report["findings"]:print(f" [{f['severity']}] {f['rule_id']} {f['location']}: {f['title']} ({f['kind']})")markdown = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("REPORT.md")["value"]
In GitHub Actions (CI gate + GitHub code scanning)
This workflow audits the spec in your repository on every pull request. It uploads the SARIF report to GitHub code scanning, so findings appear as alerts on the PR, and fails the job when anything is high or worse:
name: API security auditon:pull_request:paths: ["openapi/**"]permissions:contents: readsecurity-events: write # needed to upload SARIFjobs:audit:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- uses: actions/setup-python@v5with: { python-version: "3.12" }- run: pip install apify-client- name: Run OpenAPI Security Auditorid: auditenv:APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }}run: |python - <<'EOF'import json, os, pathlibfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("michiganpublictech/openapi-security-auditor").call(run_input={"specText": pathlib.Path("openapi/openapi.yaml").read_text(),"failOnSeverity": "high",})store = client.key_value_store(run["defaultKeyValueStoreId"])sarif = store.get_record("REPORT.sarif")if sarif:value = sarif["value"]pathlib.Path("REPORT.sarif").write_text(value if isinstance(value, str) else json.dumps(value))with open(os.environ["GITHUB_OUTPUT"], "a") as out:out.write(f"status={run['status']}\n")EOF- name: Upload SARIF to GitHub code scanningif: always() && hashFiles('REPORT.sarif') != ''uses: github/codeql-action/upload-sarif@v3with:sarif_file: REPORT.sarifcategory: openapi-security-auditor- name: Fail on findings at or above "high"if: steps.audit.outputs.status != 'SUCCEEDED'run: |echo "OpenAPI Security Auditor run status: ${{ steps.audit.outputs.status }}"exit 1
When a finding is at or above failOnSeverity, the Actor finishes with status FAILED. The reports are still saved, so the SARIF upload and the PR annotations keep working.
Tip:
specTextlets you audit a spec straight from your repository, with no public URL needed. For multi-file specs, bundle them first, for example withnpx @redocly/cli bundle openapi.yaml -o bundled.yaml.
Input
| Field | Type | Default | Description |
|---|---|---|---|
specUrls | array of strings | – (prefill: Petstore v3) | URLs of OpenAPI 3.0/3.1 or Swagger 2.0 documents (JSON or YAML). Each document is one audited API. |
specText | string | – | A full pasted document (JSON or YAML), up to 5,000,000 characters. External $refs are not fetched. |
apiBaseUrls | array of strings | – | API base URLs. If the URL itself is not a spec, up to 8 well-known paths are probed. |
runtimeChecks | boolean | false | Run passive live checks (TLS, headers, CORS, plain HTTP). Requires confirmAuthorization. |
confirmAuthorization | boolean | false | Confirms you own, or are authorized to test, the hosts contacted by runtime checks. |
minSeverity | info / low / medium / high / critical | info | Only report findings at or above this severity. The score always counts all findings. |
failOnSeverity | none / critical / high / medium / low | none | CI gate: the run fails if any reported finding is at or above this level. |
disabledRules | array of strings | – | Rule IDs to skip, e.g. OAS-QUAL-001. Needs the custom_rules feature (Pro and higher). Otherwise it is ignored and a note is added. |
licenseKey | secret string | – | Optional enterprise, prepaid or self-hosted license key. Store users don't need one. |
At least one of specUrls, specText or apiBaseUrls is required. Invalid input stops the run before anything is charged, with a readable message such as "Provide at least one of 'specUrls', 'specText' or 'apiBaseUrls'."
Example input:
{"specUrls": ["https://petstore3.swagger.io/api/v3/openapi.json"],"apiBaseUrls": ["https://api.example.com"],"runtimeChecks": true,"confirmAuthorization": true,"minSeverity": "low","failOnSeverity": "high"}
Which host do runtime checks contact?
| Target type | Host checked |
|---|---|
apiBaseUrls entry | That base URL |
specUrls entry | The first servers entry (Swagger 2.0: schemes/host/basePath). Relative servers are resolved against the spec URL. If there are none, the spec URL's origin is used. |
specText | The first absolute servers entry. Runtime checks are skipped if there is none. |
Only enable runtime checks if you are authorized for all of these hosts.
Output
Dataset: one item per API
Each item follows the schema shared by all our Actors. This is real output for the bundled Swagger 2.0 example, with findings shortened to two:
{"product": "openapi-security-auditor","product_version": "0.1.0","target": "specText (Legacy Pets API)","generated_at": "2026-10-11T17:21:28.148867Z","score": 74,"risk_level": "medium","summary": { "critical": 0, "high": 1, "medium": 5, "low": 4, "info": 2 },"findings": [{"rule_id": "OAS-TRAN-001","title": "Server URL uses plain HTTP","severity": "high","kind": "specification","category": "transport","location": "servers: http://pets.example.com/api","description": null,"evidence": { "url": "http://pets.example.com/api" },"recommendation": "Declare only `https://` servers (Swagger 2.0: `schemes: [https]`) and redirect or refuse plain-HTTP traffic at the edge.","references": ["OWASP API8:2023 Security Misconfiguration — https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/","CWE-319: Cleartext Transmission of Sensitive Information"],"confidence": 1.0,"fingerprint": "2718310ca2516ae2"},{"rule_id": "OAS-INPUT-002","title": "Unbounded array input","severity": "medium","kind": "specification","category": "input-validation","location": "POST /pets","description": null,"evidence": { "fields": { "count": 1, "examples": ["body.owner.pets"] } },"recommendation": "Set `maxItems` on array inputs. Unbounded arrays let a single request trigger arbitrarily large batch operations, memory use and downstream calls.","references": ["OWASP API4:2023 Unrestricted Resource Consumption — https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/","CWE-770: Allocation of Resources Without Limits or Throttling"],"confidence": 1.0,"fingerprint": "e0dd3597db23e38f"}],"data": {"api": "specText (Legacy Pets API)","spec_version": "2.0","title": "Legacy Pets API","api_version": "1.0","operations_count": 3,"paths_count": 2,"servers": ["http://pets.example.com/api", "https://pets.example.com/api"],"security_schemes": [{ "name": "apiKeyHeader", "type": "apiKey/header" },{ "name": "oauth", "type": "oauth2" }],"status": "ok","security_score": 74,"runtime_checks_performed": false,"findings_by_kind": { "specification": 12 }},"errors": [],"disclaimer": "Findings of kind 'specification' are derived only from what the API description declares (or omits); ... This is an automated audit, not a penetration test: ..."}
Product-specific fields in data:
| Field | Meaning |
|---|---|
api | What was audited (URL or specText (<title>)) |
status | ok, no_spec_found (base URL with runtime-only results) or failed (see errors) |
spec_version, title, api_version | From the document |
operations_count, paths_count, servers, security_schemes | Inventory of the API |
security_score | Same as score |
runtime_checks_performed | true only if live checks actually returned data |
findings_by_kind | Count of specification / runtime / observation findings, before minSeverity filtering |
spec_url | The final URL the document was loaded from, after redirects |
discovery | For apiBaseUrls: { "found": bool, "probed": n } |
runtime_target | The URL contacted by runtime checks |
A runtime finding looks the same, with "kind": "runtime":
{"rule_id": "OAS-CORS-002","title": "CORS reflects arbitrary origins","severity": "high","kind": "runtime","category": "cors","location": "GET https://api.example.com/v2","evidence": { "test_origin": "https://example.invalid", "allow_credentials": true },"recommendation": "Validate the `Origin` header against an explicit allow-list instead of echoing it back. ..."}
Key-value store
| Key | Content |
|---|---|
OUTPUT | Run summary: plan, targets, scores, billing events, notes (e.g. features not in your plan) |
REPORT.md | Human-readable Markdown report, good for PR comments and tickets |
REPORT.sarif | SARIF 2.1.0 for GitHub code scanning, GitLab, Azure DevOps and Defender for DevOps |
Score and risk level
Each API starts at 100. The first finding of each severity deducts critical 25, high 12, medium 5, low 1.5, info 0. Each further finding of the same severity deducts 60% of the previous one, so forty nits never outweigh one critical issue. Risk level: critical below 40, high below 60 or when any critical finding exists, medium below 80, then low or none. The score is deterministic: the same document always gets the same score.
Specification findings vs. runtime findings
This distinction matters, and every finding carries it in kind:
specificationfindings come only from the document. They mean "your API description declares (or fails to declare) something risky". For example,DELETE /users/{id}hassecurity: []. That is a strong signal, but the deployed gateway may still enforce auth that the spec doesn't mention, and the reverse can also be true. Treat these as design and documentation issues to confirm.runtimefindings come from what we observed on the live host during runtime checks, such as an expiring certificate, a missing HSTS header or a CORS policy that reflected our test originhttps://example.invalid. They exist only if you enabled runtime checks and confirmed authorization.observationfindings are neutral facts, for example "No OpenAPI document discovered".
Neither kind is a penetration test result. The absence of findings doesn't prove an API is secure.
Pricing
This Actor uses Apify's pay-per-event model. You pay only for delivered results:
| Event | Price | Charged when |
|---|---|---|
api-audited (primary) | $0.05 | One API description (or one API base URL) is analysed and its report is delivered |
runtime-checks | $0.02 | Passive live checks actually ran for one authorized API host, and the report was delivered |
Examples:
- Audit 1 spec: $0.05
- Audit 20 specs in CI every day for a month (600 audits): $30
- Audit 10 APIs with runtime checks: 10 × ($0.05 + $0.02) = $0.70
What is never charged:
- Unreachable URLs, documents that can't be parsed, unsupported versions, base URLs where no spec was found (unless runtime checks produced a result), and invalid input. These targets still appear in the dataset with
errors, so you can see what went wrong. runtime-checkswhen the checks were disabled, skipped or returned no data. For example, it is not charged when the host was unreachable.
Spending cap: Apify lets you set a maximum cost per run. The Actor checks your remaining budget before each API. When the cap is reached, it stops cleanly and doesn't do work you can't pay for. If your budget covers the audit but not the runtime checks, the audit still runs and the runtime checks are skipped, with a note in errors.
Prices are defined in .actor/pay_per_event.json.
Plans and licensing
- Apify Store (default): no key needed. Runs on Apify use the Marketplace plan: up to 100 APIs per run, SARIF export, the CI gate and runtime checks, all billed per event as described above.
- Pro / Team / Enterprise: a license key (
licenseKey, stored encrypted as a secret input) unlocks higher limits (500 / 2,000 / 10,000 APIs per run) anddisabledRules(custom rule selection). Enterprise adds white-label reports. - Prepaid / invoiced enterprise: a license with the
prepaidfeature turns off per-event charges for your runs. You are invoiced directly instead. - Self-hosted CI: run the Docker image in your own pipeline with a license key. Without one, local runs use the limited community plan (3 APIs per run, no runtime checks).
Invalid or expired keys never break a run. You get the standard Store experience and a visible warning. See ../../docs/licensing.md for how keys work, and contact us for enterprise terms.
Responsible use and limitations
- Only test what you're authorized to test. Runtime checks contact live hosts and run only when
runtimeChecksandconfirmAuthorizationare bothtrue. They make the requests any browser would make: one TLS handshake (plus checks for whether TLS 1.0/1.1 are still accepted), oneGETwith anOriginheader and oneOPTIONSpreflight per API. They send no credentials and do no fuzzing, brute forcing, auth bypass, exploitation or cipher enumeration. - Static analysis has limits. The Actor can only judge what the document says. Undocumented endpoints, auth enforced at a gateway, or business-logic flaws (most BOLA/BFLA cases) need manual or dynamic testing.
- Heuristics: rules such as privileged property writable (
OAS-INPUT-005) or sensitive data in response (OAS-DATA-002) use field-name patterns and carryconfidence< 1. Review them in context. - External
$refs are not fetched, for security and determinism. Bundle multi-file specs first. - Private networks are blocked. URLs that resolve to private, loopback, link-local or cloud-metadata addresses are refused on the Apify platform. Use
specTextfor internal APIs. - Size limits: documents up to 8 MB when fetched or 5 M characters when pasted, and 1,000,000 nodes after parsing (which protects against YAML alias bombs).
- Failed targets show up in the dataset with
score: 0, no findings and anerrorsentry. Filter them withdata.status == "failed".
FAQ
Does it support OpenAPI 3.1 and YAML?
Yes: Swagger 2.0, OpenAPI 3.0.x and 3.1.x, in JSON or YAML. Version 3.1 features such as type: [string, "null"] and unevaluatedProperties are understood.
My spec is private. Can I still audit it?
Yes. Paste it into specText, or post it from CI as in the GitHub Actions example. The document is analysed in memory. Findings contain only minimal evidence, and any secrets are redacted.
Will it attack my API? No. With default settings it makes no requests to your API at all, only the request that fetches the spec. With runtime checks enabled, it makes a few ordinary requests as described above.
Why is my score low when the API is actually protected by a gateway?
The score reflects the document. If your gateway enforces authentication that the spec doesn't declare, consumers, generated SDKs and other tools can't know about it either. Document it (security, securitySchemes) and the findings go away.
Can I suppress a rule?
With a Pro or higher license, use disabledRules. Disabled rules are also excluded from the score. On any plan you can use minSeverity to hide noise; that doesn't change the score.
How do I gate a pipeline?
Set failOnSeverity (for example high). The run status becomes FAILED when a reported finding meets the threshold, and the reports are still saved. See the GitHub Actions example above.
Are results stable between runs?
Yes. Rules are deterministic, and each finding has a fingerprint (rule ID + location) that you can use for baselines and diffs.
What does it cost to try? One spec costs $0.05. The prefilled Petstore example runs in a few seconds.
Rule catalog
There are 63 rules in total: 50 specification rules (OAS-AUTH, OAS-TRAN, OAS-INPUT, OAS-RATE, OAS-DATA, OAS-DEPR, OAS-INV, OAS-QUAL), 12 runtime rules (OAS-TLS, OAS-HDR, OAS-CORS) and 1 discovery observation (OAS-DISC). The full list, with severities, recommendations and OWASP/CWE/RFC references, is in RULES.md. Rule IDs are stable and never reused.
Changelog
See CHANGELOG.md.