Skip to content

feat: support API specs and Postman collections as targets#866

Open
5h4d0wr007 wants to merge 12 commits into
usestrix:mainfrom
5h4d0wr007:feat/api-pentest
Open

feat: support API specs and Postman collections as targets#866
5h4d0wr007 wants to merge 12 commits into
usestrix:mainfrom
5h4d0wr007:feat/api-pentest

Conversation

@5h4d0wr007

Copy link
Copy Markdown

Closes (#844)

Summary

Adds an api_spec target type so Strix can be pointed directly at an API contract instead of only crawling a live URL. The declared endpoints are parsed into the agent's task and the spec's base URLs are authorized as in-scope hosts, so the agent tests the full declared surface - including endpoints that aren't reachable by crawling (with correct parameter, body, and auth context).

Three input forms, all via --target:

  • OpenAPI 3.x / Swagger 2.0 file (.json / .yaml)
  • Postman collection export (.json)
  • Postman collection fetched live by id: postman://<collection-uuid>, optionally ?env=<environment-uuid> to resolve {{variables}} from a Postman environment

Motivation

For an API, the only prior option was passing a base URL as a web_application target and relying on discovery (crawling, fuzzing, JS parsing). That misses endpoints with no UI link (internal, mobile-backing, partner APIs), doesn't know declared parameter types or request bodies, and provides no systematic per-endpoint coverage. Most teams already have an OpenAPI document or a Postman collection describing exactly this surface, so feeding it in directly makes API pentests far more complete and deterministic.

What's included

  • New api_spec target type: detected in infer_target_type (spec files by content, postman:// by scheme). Parsed into a normalized endpoint inventory (method, path, parameters, body fields, auth, base URLs).
  • Parser (strix/core/api_spec.py) for OpenAPI 3.x, Swagger 2.0, and Postman v2.1, with defensive parsing and size/recursion guards.
  • Live Postman API integration: fetch_postman_collection (GET /collections/{uid}) and optional fetch_postman_environment (GET /environments/{uid}) via the official API using POSTMAN_API_KEY.
  • Full-request variable resolution: {{vars}} from the collection variable block, merged with (and overridden by) the environment, resolve in the URL/path, headers, request body, and query keys. Colon-style path variables (/pets/:id) are normalized to {id} and registered as path parameters.
  • Rich Postman extraction: query params, header names, body field names (raw JSON / urlencoded / formdata), and auth scheme with request → folder → collection inheritance.
  • Task + scope wiring (strix/core/inputs.py): the inventory renders into the root task; the spec's base URLs are authorized as in-scope web targets.
  • api_spec_testing skill: guiding spec-driven methodology (BOLA/BFLA, mass assignment, per-type injection, coverage tracking).
  • Docs: README usage section, --target CLI help + examples, docs/usage/cli.mdx, and POSTMAN_API_KEY in docs/advanced/configuration.mdx.
  • Tests: tests/test_api_spec.py (parser, fetch, environment, variable/path/auth/body extraction) and tests/test_api_spec_targets.py (target detection + input/scope wiring).

Usage

# OpenAPI / Swagger or Postman collection file, paired with the live base URL
strix -t ./openapi.yaml -t https://api.example.com
strix -t ./collection.postman_collection.json -t https://api.example.com
 
# Postman collection pulled live by id (needs POSTMAN_API_KEY)
export POSTMAN_API_KEY="PMAK-..."
strix -t postman://<collection-uid>
 
# ...with a Postman environment to resolve {{baseUrl}} / token variables
strix -t "postman://<collection-uid>?env=<environment-uid>"

Pairing the spec with the deployed base URL gives the agent both the endpoint inventory and a reachable host to attack (if Postman environment file UUID is not provided).

Design notes

  • File and API inputs converge: the Postman API fetch unwraps to the same collection shape a file has, so everything from parsing onward shares one code path.
  • Secret safety: variable values (including tokens) are resolved internally for correctness, but only field/parameter/auth names (never raw secret values) are rendered into the inventory that reaches the prompt/report.
  • Fail-fast: an api_spec target is validated (file parsed / collection fetched) during CLI arg parsing, so a bad key, missing collection, or empty spec errors immediately with an actionable message.

Known limitations / possible future work

  • Remote spec files by URL aren't auto-detected (local files only); the Postman API path covers the "no local file" case.
  • Query-parameter values aren't surfaced (keys only), by the same secret-safety policy as headers/bodies.
  • Base URLs declared in a spec as localhost aren't rewritten to the sandbox host gateway the way an explicit --target URL is; pass the reachable host explicitly for local targets.

Testing

  • uv run pytest tests/test_api_spec.py tests/test_api_spec_targets.py -v
Screenshot 2026-07-23 at 6 17 58 PM
  • Manually verified end to end against a locally deployed OWASP crAPI instance using both a local spec file and a live postman://…?env=… collection.
Screenshot 2026-07-23 at 6 01 40 PM

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds API-contract targets throughout the CLI and scan-input pipeline.

  • Parses OpenAPI 3.x, Swagger 2.0, local Postman exports, and live Postman collections.
  • Renders normalized endpoints into the root-agent task and adds declared API hosts to scan scope.
  • Adds Postman API-key configuration, API-testing guidance, documentation, dependencies, and tests.

Confidence Score: 1/5

This PR is not safe to merge until live Postman targets can start, API inventories preserve common schema semantics, and specifications cannot expand authorized testing scope.

The documented live Postman path raises a key error, common OpenAPI and Swagger constructs produce incorrect endpoint inventories, and spec-controlled server URLs are promoted directly into the platform-verified authorization set.

strix/interface/main.py, strix/interface/utils.py, strix/core/api_spec.py, strix/core/inputs.py

Security Review

API-spec server URLs are promoted directly into the platform-verified authorization list without being matched to an explicitly selected host, allowing a supplied contract to expand active testing scope.

Important Files Changed

Filename Overview
strix/core/api_spec.py Implements the new parser and Postman integration, but mishandles explicit no-auth overrides and drops body fields from referenced schemas.
strix/interface/main.py Adds fail-fast API-spec validation, but a mismatched Postman identifier key prevents every live Postman target from starting.
strix/interface/utils.py Adds content-based local-spec detection and Postman URI parsing; its normalized collection_uid key is not consumed consistently by the CLI.
strix/core/inputs.py Threads API inventories into root tasks but authorizes every spec-declared host without requiring an independently selected target.
strix/config/settings.py Adds the aliased Postman API-key integration setting consistently with the existing configuration model.
tests/test_api_spec.py Covers primary parser paths but omits referenced request schemas and operation-level empty security overrides.
tests/test_api_spec_targets.py Covers target and scope wiring but does not exercise the full CLI parsing path or constrain spec-declared hosts to explicit targets.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
strix/interface/main.py:659
**Postman identifier key mismatch**

When a fresh scan uses `postman://<collection-uid>`, target inference stores the identifier as `collection_uid`, but this validation branch reads `collection_uuid`, causing a `KeyError` before the collection is fetched.

### Issue 2 of 4
strix/core/api_spec.py:237
**Empty security override is lost**

When an operation declares `security: []` under a nonempty global security policy, the falsy result falls back to `global_security`, incorrectly labeling an explicitly unauthenticated endpoint as authenticated and giving the agent the wrong testing context. The Swagger branch at line 287 has the same behavior.

### Issue 3 of 4
strix/core/api_spec.py:198-202
**Referenced body fields are dropped**

When an OpenAPI request body uses a local `$ref` or schema composition such as `allOf`, this code only checks direct `schema.properties` and emits an empty field list, removing declared body fields from the agent's endpoint inventory and operation-specific testing context.

### Issue 4 of 4
strix/core/inputs.py:138-140
**Spec URLs bypass target authorization**

When a supplied specification declares an unrelated server URL, this code promotes that URL directly into the platform-verified authorized target list without matching it against an explicit user-selected host, causing the autonomous scanner to treat the unrelated host as approved for active pentesting.

Reviews (1): Last reviewed commit: "feat: support API specs and Postman coll..." | Re-trigger Greptile

Comment thread strix/interface/main.py Outdated
if target_dict.get("source") == "postman_api":
postman_key = load_settings().integrations.postman_api_key or ""
inventory = parse_postman_api(
target_dict["collection_uuid"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Postman identifier key mismatch

When a fresh scan uses postman://<collection-uid>, target inference stores the identifier as collection_uid, but this validation branch reads collection_uuid, causing a KeyError before the collection is fetched.

Knowledge Base Used: CLI and Runner: from strix invocation to a running scan

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/interface/main.py
Line: 659

Comment:
**Postman identifier key mismatch**

When a fresh scan uses `postman://<collection-uid>`, target inference stores the identifier as `collection_uid`, but this validation branch reads `collection_uuid`, causing a `KeyError` before the collection is fetched.

**Knowledge Base Used:** [CLI and Runner: from `strix` invocation to a running scan](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/cli-and-runner.md)

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: postman://… now fails cleanly via parser.error (e.g. 401 Check POSTMAN_API_KEY) instead of crashing, and ?env= is now honoured cleanly(issue bc of an accidental previous commit).

Comment thread strix/core/api_spec.py
for p in (*shared_params, *(op.get("parameters", []) or []))
if isinstance(p, dict)
]
op_security = _security_names(op.get("security")) or global_security

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Empty security override is lost

When an operation declares security: [] under a nonempty global security policy, the falsy result falls back to global_security, incorrectly labeling an explicitly unauthenticated endpoint as authenticated and giving the agent the wrong testing context. The Swagger branch at line 287 has the same behavior.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/core/api_spec.py
Line: 237

Comment:
**Empty security override is lost**

When an operation declares `security: []` under a nonempty global security policy, the falsy result falls back to `global_security`, incorrectly labeling an explicitly unauthenticated endpoint as authenticated and giving the agent the wrong testing context. The Swagger branch at line 287 has the same behavior.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In OpenAPI/Swagger, security: [] on an operation is an explicit opt-out that overrides a non-empty global policy; a missing security key is what inherits the global.

Verified: /public (security: []) → []; /private (no key) -> inherits global.

Comment thread strix/core/api_spec.py
Comment on lines +198 to +202
for media in content.values():
schema = media.get("schema") if isinstance(media, dict) else None
props = schema.get("properties") if isinstance(schema, dict) else None
if isinstance(props, dict):
return list(props.keys())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Referenced body fields are dropped

When an OpenAPI request body uses a local $ref or schema composition such as allOf, this code only checks direct schema.properties and emits an empty field list, removing declared body fields from the agent's endpoint inventory and operation-specific testing context.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/core/api_spec.py
Line: 198-202

Comment:
**Referenced body fields are dropped**

When an OpenAPI request body uses a local `$ref` or schema composition such as `allOf`, this code only checks direct `schema.properties` and emits an empty field list, removing declared body fields from the agent's endpoint inventory and operation-specific testing context.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: referenced/composed request bodies drop the fields.

Comment thread strix/core/inputs.py
Comment on lines +138 to +140
authorized.extend(
{"type": "web_application", "value": base_url, "workspace_path": ""}
for base_url in inventory.base_urls

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Spec URLs bypass target authorization

When a supplied specification declares an unrelated server URL, this code promotes that URL directly into the platform-verified authorized target list without matching it against an explicit user-selected host, causing the autonomous scanner to treat the unrelated host as approved for active pentesting.

Knowledge Base Used: CLI and Runner: from strix invocation to a running scan

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/core/inputs.py
Line: 138-140

Comment:
**Spec URLs bypass target authorization**

When a supplied specification declares an unrelated server URL, this code promotes that URL directly into the platform-verified authorized target list without matching it against an explicit user-selected host, causing the autonomous scanner to treat the unrelated host as approved for active pentesting.

**Knowledge Base Used:** [CLI and Runner: from `strix` invocation to a running scan](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/cli-and-runner.md)

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected. A spec/collection under pentest is expected to have user verified URLs. But to add an extra control, here's the new design: the environment-driven workflow is intentional, so a spec/collection self-authorizes the hosts it declares when no explicit host is given (strix -t postman://?env= still works with no -t). When the user also passes explicit -t targets, those act as an allow-list that narrows the declared URLs, so a stray example/prod server can't expand scope past what the user selected. See _explicit_target_hosts.

Verified: env-only -> declared base URLs authorized; spec + explicit host -> only the matching host kept, unrelated declared hosts dropped. Tests updated in tests/test_api_spec_targets.py (self-authorize, narrowing, and match cases).

@bearsyankees

Copy link
Copy Markdown
Collaborator

@5h4d0wr007 i think the idea is great -- wanna review the greptile findings?

@5h4d0wr007 5h4d0wr007 left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bearsyankees - these are all reviewed and updated. Please feel free to verify and re-run Greptile. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Ingest API specs (OpenAPI / Postman collections) as a first-class target type

2 participants