# OpenAPI Security Auditor (`michiganpublictech/openapi-security-auditor`) Actor

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.

- **URL**: https://apify.com/michiganpublictech/openapi-security-auditor.md
- **Developed by:** [Justin Hagerty](https://apify.com/michiganpublictech) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $50.00 / 1,000 api auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## OpenAPI Security Auditor

**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 `operationId`s, undocumented operations, no 4xx or 401/403 responses, responses without schemas, no contact or security contact, unresolvable or external `$ref`s, 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](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 `$ref` resolution.** 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.com` and 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 `kind` is `specification` (what the document declares), `runtime` (what we observed on the live host) or `observation` (neutral facts). A declared issue is never presented as a confirmed vulnerability.
- **Passive, opt-in runtime checks.** One TLS handshake, one `GET` and 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:** `failOnSeverity` gate, 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

1. Open the Actor and click **Try for free**.
2. 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.
3. *(Optional)* Turn on **Run passive runtime checks** and tick **I am authorized to test these hosts**.
4. Click **Start**. Each API takes a few seconds.
5. 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.sarif` and the `OUTPUT` run summary.

#### Via the API (`curl`)

This runs the Actor synchronously and returns the dataset items, meaning one report per API:

```bash
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`)

```python
from apify_client import ApifyClient

client = 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:

```yaml
name: API security audit
on:
  pull_request:
    paths: ["openapi/**"]

permissions:
  contents: read
  security-events: write   # needed to upload SARIF

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install apify-client
      - name: Run OpenAPI Security Auditor
        id: audit
        env:
          APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }}
        run: |
          python - <<'EOF'
          import json, os, pathlib
          from apify_client import ApifyClient

          client = 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 scanning
        if: always() && hashFiles('REPORT.sarif') != ''
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: REPORT.sarif
          category: 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: `specText` lets you audit a spec straight from your repository, with no public URL needed. For multi-file specs, bundle them first, for example with `npx @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 `$ref`s 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:

```json
{
  "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:

```json
{
  "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"`:

```json
{
  "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`:

- **`specification`** findings come only from the document. They mean *"your API description declares (or fails to declare) something risky"*. For example, `DELETE /users/{id}` has `security: []`. 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.
- **`runtime`** findings 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 origin `https://example.invalid`. They exist only if you enabled runtime checks and confirmed authorization.
- **`observation`** findings 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-checks` when 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`](.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) and `disabledRules` (custom rule selection). Enterprise adds white-label reports.
- **Prepaid / invoiced enterprise:** a license with the `prepaid` feature 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](../../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 `runtimeChecks` **and** `confirmAuthorization` are both `true`. They make the requests any browser would make: one TLS handshake (plus checks for whether TLS 1.0/1.1 are still accepted), one `GET` with an `Origin` header and one `OPTIONS` preflight 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 carry `confidence` < 1. Review them in context.
- **External `$ref`s 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 `specText` for 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 an `errors` entry. Filter them with `data.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](RULES.md)**. Rule IDs are stable and never reused.

### Changelog

See [CHANGELOG.md](CHANGELOG.md).

# Changelog

This Actor's version history is a separate document: https://apify.com/michiganpublictech/openapi-security-auditor/changelog.md

# Actor input Schema

## `specUrls` (type: `array`):

Public URLs of OpenAPI 3.0/3.1 or Swagger 2.0 documents (JSON or YAML). Each document is one audited API (one 'api-audited' event).

## `specText` (type: `string`):

Paste a complete OpenAPI 3.0/3.1 or Swagger 2.0 document (JSON or YAML) here, e.g. from your repository. Useful for private APIs whose documentation is not publicly reachable. External $refs are not fetched — bundle the document first.

## `apiBaseUrls` (type: `array`):

Base URLs of public APIs, e.g. https://api.example.com. If the URL itself is not an API description, the Actor probes a fixed list of 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).

## `runtimeChecks` (type: `boolean`):

Also inspect the live API host: TLS certificate and protocol versions, security headers, CORS behaviour and whether content is served over plain HTTP. Uses one TLS handshake (plus TLS 1.0/1.1 acceptance probes), one GET and one CORS preflight OPTIONS request per API — no fuzzing, no credentials, no exploitation. The host checked is the API base URL, or the first server declared in the document. Charged as 'runtime-checks' per API, only when the checks actually ran. Requires 'I am authorized to test these hosts'.

## `confirmAuthorization` (type: `boolean`):

Confirm that you own, or have explicit permission to test, every API host contacted by runtime checks (the API base URLs you list and the servers declared in the documents you supply). Runtime checks never run without this confirmation.

## `minSeverity` (type: `string`):

Only include findings at or above this severity in the output. The score is always computed from all findings, so filtering does not change it.

## `failOnSeverity` (type: `string`):

CI gate: mark the run as FAILED when any reported finding is at or above this severity. Results are still saved. 'none' disables the gate.

## `disabledRules` (type: `array`):

Rule IDs to skip, e.g. OAS-QUAL-001. See the rule catalog (RULES.md). Requires a plan with the 'custom_rules' feature (Pro and above); otherwise ignored with a note.

## `licenseKey` (type: `string`):

Optional. Enterprise, prepaid or self-hosted license key (AFY1.…). Apify Store users do not need one.

## Actor input object example

```json
{
  "specUrls": [
    "https://petstore3.swagger.io/api/v3/openapi.json"
  ],
  "runtimeChecks": false,
  "confirmAuthorization": false,
  "minSeverity": "info",
  "failOnSeverity": "none"
}
```

# Actor output Schema

## `results` (type: `string`):

One report per target: score, risk level, findings and product data.

## `summary` (type: `string`):

Plan, per-target scores and billing summary for the run (OUTPUT record).

## `reportMarkdown` (type: `string`):

Human-readable report of all findings (REPORT.md).

## `reportSarif` (type: `string`):

SARIF 2.1.0 report for GitHub code scanning and other CI tools (REPORT.sarif).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "specUrls": [
        "https://petstore3.swagger.io/api/v3/openapi.json"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("michiganpublictech/openapi-security-auditor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "specUrls": ["https://petstore3.swagger.io/api/v3/openapi.json"] }

# Run the Actor and wait for it to finish
run = client.actor("michiganpublictech/openapi-security-auditor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "specUrls": [
    "https://petstore3.swagger.io/api/v3/openapi.json"
  ]
}' |
apify call michiganpublictech/openapi-security-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,michiganpublictech/openapi-security-auditor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/4EBTf6EWuJIYgqTAz/builds/94UW7WSvjyxl53mOw/openapi.json
