Skip to content

feat(graybox): OWASP API Security Top 10 (2023) implementation#406

Open
toderian wants to merge 90 commits into
developfrom
feat/redmesh-api-top10
Open

feat(graybox): OWASP API Security Top 10 (2023) implementation#406
toderian wants to merge 90 commits into
developfrom
feat/redmesh-api-top10

Conversation

@toderian
Copy link
Copy Markdown
Contributor

No description provided.

toderian and others added 30 commits May 13, 2026 19:53
Locks in the scenario ID prefix for the new graybox OWASP API Top 10 2023
probe families before any catalog or probe code is written. Disambiguates
from existing PT-A<NN>-<NN> web-app IDs which differ by only one character
in position 5.

Implements Subphase 1.0 commit #1 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the permissive `PT-[A-Z0-9]+-\d+` catch-all with explicit
alternation over the three valid prefixes documented in the ADR:
PT-A<NN>-<NN>, PT-API7-<NN>, and PT-OAPI<N>-<NN>. Adds positive and
negative test cases covering the boundary IDs (PT-OAPI10-01) and the
visually-ambiguous typo `PT-API1-01` that the new convention prevents.

Implements Subphase 1.0 commit #2 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds eight frozen-dataclass endpoint sub-models for the OWASP API Top 10
2023 graybox families:

  - ApiObjectEndpoint     — BOLA (PT-OAPI1-01)
  - ApiPropertyEndpoint   — BOPLA read+write (PT-OAPI3-01/02)
  - ApiFunctionEndpoint   — BFLA read+mutating (PT-OAPI5-01..04)
  - ApiResourceEndpoint   — bounded resource consumption (PT-OAPI4-*)
  - ApiBusinessFlow       — sensitive flow abuse (PT-OAPI6-*)
  - ApiTokenEndpoint      — broken-auth probes (PT-OAPI2-01..03)
  - ApiInventoryPaths     — inventory mismanagement (PT-OAPI9-*)
  - ApiSecurityConfig     — aggregating wrapper

ApiOutboundEndpoint is deliberately absent: API10 is deferred to Phase 9
until callback-receiver infrastructure exists.

Mirrors the existing IdorEndpoint / JwtEndpoint shape (from_dict ctor,
sensible defaults, frozen=True). GrayboxTargetConfig is not yet wired —
that lands in Subphase 1.1 commit #2.

Implements Subphase 1.1 commit #1 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the api_security field to GrayboxTargetConfig (default-empty
ApiSecurityConfig) and routes the new section through from_dict so a
launch payload can carry the OWASP API Top 10 endpoint configs end-to-end
without any other plumbing.

Implements Subphase 1.1 commit #2 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds TestApiSecurityConfig covering all eight new sub-models:
- Per-endpoint defaults and full from_dict round-trip
- Missing-required-key behaviour (raises KeyError for `path`)
- ApiSecurityConfig default lists (ssrf body fields, tampering fields,
  debug paths, OpenAPI candidates)
- GrayboxTargetConfig wiring (default api_security, payload propagation,
  KeyError on malformed nested payload)

16 new test methods. Existing 18 unchanged.

Implements Subphase 1.1 commit #3 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends the catalog schema with an optional `attack: list[str]` field
and registers the 23 OWASP API Top 10 v1 scenarios:

  API1 (BOLA):    1 entry  (PT-OAPI1-01)
  API2 (auth):    3 entries (PT-OAPI2-01..03)
  API3 (BOPLA):   2 entries (PT-OAPI3-01, -02)
  API4 (resource): 3 entries (PT-OAPI4-01..03)
  API5 (BFLA):    4 entries (PT-OAPI5-01..04)
  API6 (flows):   2 entries (PT-OAPI6-01, -02)
  API8 (misconfig): 5 entries (PT-OAPI8-01..05)
  API9 (inventory): 3 entries (PT-OAPI9-01..03)

Per-family attribution (`api_access`/`api_auth`/`api_data`/`api_config`/
`api_abuse`) matches the five-family probe split landing in Subphase 1.3.
API7 SSRF keeps its legacy ID `PT-API7-01`.

Side fix: legacy `PT-API7-01` `owasp` tag was `A10:2021` in the catalog
but the probe code already emits `API7:2023`. Catalog now agrees with
probe so the new Navigator §3.3.3 dispatch picks it up.

Adds helpers `graybox_scenario(id)` and `attack_for_scenario(id)` so
emit helpers (Subphase 1.6) can use the catalog as the runtime source
of truth for ATT&CK defaults.

API10 (Unsafe Consumption) intentionally absent — Phase 9 follow-up.

Implements Subphase 1.2 commit #1 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promotes the MITRE ATT&CK mapping to a v1 contract: every PT-OAPI* and
PT-API7-01 catalog entry MUST declare a non-empty `attack` list. The
catalog (not probe code or this markdown plan) is the executable source
of truth for the default attack mapping; ProbeBase.emit_vulnerable will
read it via `attack_for_scenario(id)` in Subphase 1.6.

Also:
- Bumps the graybox inventory floor from 80 to 103 (legacy 80 + 23 new
  PT-OAPI* entries).
- Adds a test for `attack_for_scenario` covering known/legacy/unknown ids.
- Adds a count + per-category coverage assertion (8 categories ≥1 entry,
  no PT-OAPI10-* in v1).

Note: the "widen regex" commit (#2 in the plan's Subphase 1.2 list) was
landed earlier in Subphase 1.0 commit #2 (1d8d07e) so it isn't duplicated
here.

Implements Subphase 1.2 commit #3 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First of the five OWASP API Top 10 probe families introduced by the
five-family split (amendment #4). Covers API1 (BOLA) and API5 (BFLA).
Skeleton only — `run()` returns no findings until the concrete probe
methods land in Phases 2.1, 2.3, and 3.4.

Implements Subphase 1.3 commit #1 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers OWASP API2 — Broken Authentication. Skeleton; concrete probes
land in Phase 2.6 (PT-OAPI2-01/02) and Phase 3.x (PT-OAPI2-03 stateful).

Implements Subphase 1.3 commit #2 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers OWASP API3 — Broken Object Property Level Authorization (BOPLA).
Skeleton; concrete probes land in Phase 2.2 (read-side excessive
exposure) and Phase 3.1 (stateful mass-assignment write).

Implements Subphase 1.3 commit #3 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers OWASP API8 (Security Misconfiguration) and API9 (Improper Inventory
Management). Skeleton; concrete probes land in Phase 2.4 and Phase 2.5.

Implements Subphase 1.3 commit #4 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final of the five OWASP API Top 10 probe families. Covers API4
(Unrestricted Resource Consumption) and API6 (Unrestricted Access to
Sensitive Business Flows). Skeleton; concrete probes land in Phase 3.2
(bounded resource consumption) and Phase 3.3 (stateful flow abuse).

All five API probe families are now registered.

Implements Subphase 1.3 commit #5 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two new tests on the registry side (test_target_config.py):
- test_registry_has_expected_probes asserts every legacy + new API
  family key is present.
- test_api_family_classes_importable resolves each module-relative
  dotted path, instantiates the class, and verifies capability flags.

Two new tests on the worker side (test_worker.py):
- test_supported_features_include_api_top10_families confirms the
  five new keys flow through GrayboxLocalWorker.get_supported_features().
- test_api_family_skeletons_dispatch_cleanly instantiates each new
  family against a minimal mocked context and asserts run() returns
  an empty list (skeleton behaviour expected before Phase 2/3 probes
  are wired).

Implements Subphase 1.3 commit #6 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The launch path already deep-copies the `target_config` dict into the
persisted JobConfig and forwards it to the worker, which parses it via
GrayboxTargetConfig.from_dict (extended in Subphase 1.1 to handle the
new `api_security` section). No filter strips unknown keys; the only
mutation is `_apply_launch_safety_policy` normalising `discovery`.

This commit documents the passthrough contract in the docstring so
future contributors do not assume new target_config sections need
explicit allowlisting at the launch boundary.

Implements Subphase 1.4 commit #1 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Asserts that a launch with a populated `target_config.api_security`
section round-trips through `launch_webapp_scan` into the persisted
JobConfig: object_endpoints (with tenant_field), function_endpoints
(with revert_path), token_endpoints (with logout_path), and
inventory_paths (with deprecated_paths) are all preserved verbatim.

Implements Subphase 1.4 commit #2 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operator-facing reference for the OWASP API Top 10 target_config section
introduced in Subphase 1.1. Documents every endpoint sub-model, which
scenario IDs they drive, which fields are required, stateful gating
expectations, and the API10 / auth / budget forward-reference notes.

Minimal-config example at the bottom for quick-start.

Implements Subphase 1.4 commit #3 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…FormAuth

Introduces the strategy pattern that lets graybox auth handle Bearer and
API-key targets in addition to form-login. This commit only adds the new
infrastructure; the existing AuthManager remains the active code path
until Subphase 1.5 commit #3 wires it to the strategy dispatcher.

New `graybox/auth_strategies.py`:
- `AuthStrategy` ABC with `preflight()`, `authenticate(creds)`, `refresh()`,
  `cleanup()`, and a shared `make_session()` helper.
- `FormAuth(AuthStrategy)` carrying the existing form-login behaviour
  (CSRF auto-detection, robust success detection, hidden-input fallback).
  Behaviour is identical to the legacy inline logic; copies are
  intentional so the orchestrator can switch over in commit #3 without
  intermediate breakage.

Design note: package layout deviates slightly from the plan's
`graybox/auth/` package suggestion. Sibling module `auth_strategies.py`
preserves all existing import paths (`from .auth import AuthManager`,
~15 callers + ~10 test patches) and is functionally equivalent. Can be
re-organised into a package later if it grows.

Implements Subphase 1.5 commit #1 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mutable credential bundle handed by AuthManager to each AuthStrategy
at authenticate() time. Covers form (username/password), Bearer
(bearer_token + optional bearer_refresh_token), and API-key (api_key).

Secret-handling contract:
- Never serialised — no to_dict(), no JSON. The persisted JobConfig
  carries only `secret_ref` + non-secret capability flags (Subphase 1.5
  commit #8).
- __repr__ overridden to expose only boolean has_* flags, never values.
- clear() overwrites every field with empty strings; called by
  AuthManager on cleanup so accidental references see no historical
  secrets.

Implements Subphase 1.5 commit #2 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…trator

AuthManager now delegates form-login to FormAuth and preflight checks to
the strategy's preflight() method. The orchestrator owns lifecycle
(expiry, retry, multi-principal coordination, cleanup) while the strategy
owns protocol-level details (CSRF detection, success heuristics).

Mechanical changes:
- `_try_login_attempt` builds a FormAuth, hands it a Credentials VO,
  catches `requests.RequestException` to classify retryable failures.
- `preflight_check` delegates to strategy.preflight().
- `_is_login_success`, `_extract_csrf`, `_find_csrf_value` removed from
  AuthManager (now live on FormAuth verbatim).
- `extract_csrf_value` static helper delegates to FormAuth (preserves
  the public probe-facing API surface).
- `detected_csrf_field` property unchanged — populated via
  `strategy.last_detected_csrf_field` after each auth attempt.
- FormAuth.authenticate raises `requests.RequestException` on transport
  errors so the orchestrator can drive the retry path.

Test updates (no behaviour change, only refactor accommodation):
- `TestCsrfAutoDetect` instantiates FormAuth directly and exercises
  `_extract_csrf` there; the standalone-helper variant of the legacy
  `test_csrf_field_property` was reshaped to test `last_detected_csrf_field`.
- `TestLoginSuccessDetection._check` calls FormAuth._is_login_success.
- All `requests` patches updated from `auth.requests` to
  `auth_strategies.requests` since that's where the HTTP calls now happen.
- `test_authenticate_retries_transient_transport_error` drops the
  leading-anon-session MagicMock from `Session.side_effect` (the anon
  session is built via auth.requests, which is unpatched in the test).

Implements Subphase 1.5 commit #3 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… to GrayboxTargetConfig

`AuthDescriptor` carries the non-secret auth configuration for graybox:
auth_type selector, header/scheme/location knobs, optional Bearer
refresh URL, and `authenticated_probe_path` used by strategy-aware
preflight.

Secret values (bearer_token, api_key, bearer_refresh_token) are
deliberately absent — they travel as top-level launch parameters and
land in the R1FS secret payload (Subphase 1.5 commit #8).

Wired into ApiSecurityConfig as the `auth` field with a default-form
AuthDescriptor so existing form-login launches continue to work
without any config change.

Implements Subphase 1.5 commit #4 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`_build_strategy` now resolves `auth_type` from
`target_config.api_security.auth` and dispatches to the appropriate
AuthStrategy. The default (``form``) continues to route to FormAuth so
existing graybox launches behave identically.

Non-form auth types raise NotImplementedError until Subphase 1.5
commits #6 (bearer) and #7 (api_key) land — explicit failure is better
than silently dispatching to the wrong strategy.

`preflight_check` (already strategy-delegating from commit #3) now
correctly preflights against whichever strategy `auth_type` selects.

Implements Subphase 1.5 commit #5 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`BearerAuth` injects `creds.bearer_token` into every request via the
configured header/scheme (default `Authorization: Bearer <token>`).
No HTTP traffic during `authenticate`; preflight optionally HEADs
`auth.authenticated_probe_path` and rejects 401/403 responses.

Header name, scheme, and an optional `authenticated_probe_path` are
sourced from `target_config.api_security.auth` (AuthDescriptor). The
strategy gracefully degrades to defaults when ApiSecurityConfig is
absent, so unit tests with minimal fixtures continue to work.

Wired into `AuthManager._build_strategy` so launches with
`auth.auth_type='bearer'` automatically route through this strategy.

Implements Subphase 1.5 commit #6 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`ApiKeyAuth` places `creds.api_key` either in a header (default,
`X-Api-Key` configurable) or a query parameter
(`auth.api_key_location='query'`, `auth.api_key_query_param`).

Query-parameter placement is supported for legacy interoperability —
the Subphase 1.6 evidence scrubber will redact the configured param
name from finding evidence; the Navigator launch form shows a warning
banner (Subphase 8.5). Header is preferred and is the default.

Wired into `AuthManager._build_strategy` — `auth_type='api_key'` now
dispatches here; unknown auth types raise ValueError (was
NotImplementedError) since the dispatch table is now complete.

Implements Subphase 1.5 commit #7 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e scrubbing

OWASP API Top 10 secrets travel as top-level launch parameters
(mirroring official_password), get persisted into the R1FS secret
payload alongside form credentials, and are blanked from the publicly
archived JobConfig before put_job_config().

Changes:
- `models/archive.py::JobConfig`: add runtime-only secret fields
  `bearer_token`, `api_key`, `bearer_refresh_token` plus non-secret
  capability flags `has_bearer_token`, `has_api_key`,
  `has_bearer_refresh_token`.
- `services/secrets.py`:
  - `build_graybox_secret_payload(...)`: accept the three new secret
    kwargs.
  - `persist_job_config_with_secrets(...)`: extract them from the
    config, push into the secret payload, set capability flags on the
    persisted config, then `_blank_graybox_secret_fields` strips raw
    values before archive write.
  - `_blank_graybox_secret_fields(...)`: also blanks the three new
    fields.
  - `resolve_job_config_secrets(...)`: repopulates the three runtime
    fields from the secret payload at worker startup.
- `services/launch_api.py::launch_webapp_scan`: accept top-level
  `bearer_token`, `api_key`, `bearer_refresh_token` kwargs. Replace
  the unconditional "official credentials required" check with
  auth-type-aware validation (form requires user+pass; bearer
  requires bearer_token; api_key requires api_key). Pass through
  the three new secrets to `_persist_and_announce_pentest_job`.
- `_persist_and_announce_pentest_job`: accept the three new secret
  params, pass them to JobConfig.

Implements Subphase 1.5 commit #8 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New test classes:
- TestBearerAuthStrategy: default + custom header/scheme; empty token
  rejected; refresh round-trip; preflight skipped without probe_path
  and returns error on 401.
- TestApiKeyAuthStrategy: header vs query placement; unknown location
  rejected; empty key rejected.
- TestAuthManagerStrategyDispatch: AuthManager._build_strategy routes
  correctly to FormAuth / BearerAuth / ApiKeyAuth based on
  target_config.api_security.auth.auth_type, and raises ValueError on
  unknown types.

Existing form-login tests unchanged; all 41 pass.

Implements Subphase 1.5 commit #9 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nce, or LLM input

New `tests/test_secret_isolation.py` enforces the secret-handling
contract from Subphase 1.5:

- TestSecretIsolationInBuildPayload: build_graybox_secret_payload
  carries the three new secrets; _blank_graybox_secret_fields zeroes
  them.
- TestSecretIsolationInPersistedConfig: persist_job_config_with_secrets
  produces a JobConfig with `bearer_token`/`api_key`/
  `bearer_refresh_token` blanked, `has_*` capability flags set, and a
  populated `secret_ref`. Worker-side `resolve_job_config_secrets`
  repopulates the runtime fields from the secret payload.
- TestSecretIsolationInCredentialsRepr: Credentials.__repr__ shows only
  capability booleans, never secret values.

Note: GrayboxFinding evidence redaction lives in Subphase 1.6 (the
centralised scrubber); this test focuses on the persistence boundary.
The full LLM-input boundary check is exercised by
test_llm_input_isolation.py (extended in Subphase 1.6).

Implements Subphase 1.5 commit #10 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nclusive helpers

Introduces three single-call helpers that probe families use instead of
constructing GrayboxFinding by hand. Two benefits:

- Reduces boilerplate (the typical 8-10-line GrayboxFinding(...) call
  becomes a single emit_vulnerable(...)).
- Provides a single point at which evidence redaction will be enforced
  once the centralised scrubber lands in Subphase 1.6 commit #2.

Helpers:
- emit_vulnerable(scenario_id, title, severity, owasp, cwe, evidence,
    *, attack=None, evidence_artifacts=None, replay_steps=None,
    remediation=None): default attack mapping resolved from
    `scenario_catalog.attack_for_scenario(scenario_id)` so probes do not
    carry per-scenario ATT&CK lists in code.
- emit_clean(scenario_id, title, owasp, evidence): not_vulnerable / INFO.
- emit_inconclusive(scenario_id, title, owasp, reason): records the
  reason as `evidence=["reason=<value>"]` for downstream grouping.

Existing probe families (PT-A* / PT-API7-01) are unchanged for now —
migration to the helpers is a follow-up cleanup.

Implements Subphase 1.6 commit #1 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `scrub_graybox_secrets(value, *, secret_field_names=())` to
graybox/findings.py and wires it into:

  - GrayboxFinding.to_flat_finding (final storage-boundary pass)
  - ProbeBase.emit_vulnerable / emit_clean / emit_inconclusive
    (pre-emission scrub via _scrub_for_emission, with configured names
    pulled from target_config.api_security.auth)

Generic patterns redact:
  - Authorization: <…> (full header value to next field separator)
  - Cookie / Set-Cookie headers (same)
  - JWTs (eyJ…, three base64url chunks)
  - Bare `Bearer <token>` references
  - name=value forms for password / secret / token / api_key / apikey
  - JSON `"name": "value"` for the same names + bearer_token + api*key

Per-call extension via `secret_field_names`: ProbeBase passes the
configured API-key header name + query param name + Bearer header name
from AuthDescriptor so custom names (X-Customer-Key, etc.) are also
scrubbed before the finding crosses the storage boundary.

Defense-in-depth: the storage scrubber runs even on findings that were
emitted before Subphase 1.6 helpers existed, so legacy probes that
construct GrayboxFinding directly cannot leak.

Implements Subphase 1.6 commit #2 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New tests/test_findings_redaction.py covering scrub_graybox_secrets
and to_flat_finding pass-through:

- TestScrubGenericPatterns (10 cases): Authorization/Cookie/Set-Cookie
  headers, bare JWTs, Bearer tokens, password/token/api_key/apikey
  k=v forms, JSON bearer_token, embedded headers in compound evidence
  strings.
- TestScrubConfiguredNames (2 cases): custom header + custom query
  parameter names supplied via secret_field_names.
- TestScrubRecursive (3 cases): list / dict recursion; non-string
  passthrough.
- TestToFlatFindingScrubs (1 case): an end-to-end GrayboxFinding with
  three secret patterns and four non-secret fields confirms the
  storage-boundary scrubber strips secrets while preserving asset
  identifiers, scenario_id, severity, etc.

Implements Subphase 1.6 commit #3 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New TestApiAuthSecretsScrubbed class (4 cases) verifying that API-flavoured
secret patterns never reach the LLM input even when carried in fields
that the build_llm_input pipeline forwards:

- Authorization: Bearer <jwt> in evidence — scrubbed end-to-end.
- Cookie: sessionid=<value> — scrubbed.
- password=<value> in evidence k=v form — scrubbed.
- API-key in URL query param — scrubbed regardless of whether the
  carrier field is dropped (legacy `evidence`) or forwarded
  (`evidence_items`/title/description).

Each case drives a real GrayboxFinding through to_flat_finding and then
through build_llm_input so both the storage-boundary scrubber and the
LLM input pipeline are exercised together. The contract: the secret
value's exact string must not appear in repr(out.findings).

Implements Subphase 1.6 commit #4 of the API Top 10 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
toderian added 5 commits May 13, 2026 20:34
What changed:
- carry regular bearer/api-key secrets through the existing secret_ref lane
- fail closed on malformed graybox secret docs and validate target_config at launch
- validate API-native sessions with a real authenticated request and strengthen finding identity

Why:
- API Top 10 probes need durable low-privilege API credentials and non-vacuous launch/auth failures to produce trustworthy findings.
What changed:
- emit inconclusive API scenario findings for missing target_config inventory
- require low-privilege sessions for BOLA and business-flow abuse checks
- add operator opt-ins for higher-risk API4/API8 probes and avoid official-account fallback
- report mutated-but-unverified stateful probes as inconclusive instead of clean

Why:
- completed scans should explain skipped API Top 10 coverage and avoid unsafe or misleading probe outcomes.
What changed:
- unwrap launch/status/archive responses from the deployment result envelope
- poll get_job_status instead of the stale job_status route
- handle flat evidence strings in manifest assertions
- make the stateful-gated scenario assert non-vacuous inconclusive findings

Why:
- the API Top 10 e2e harness must catch missing findings and broken API contracts instead of passing on empty or misread responses.
What changed:
- move discovery max-pages parsing back into _extract_discovery_max_pages after target_config validation was added

Why:
- launch safety policy must preserve route-discovery caps instead of returning None for valid target_config payloads.
What changed:
- append backend memory for API Top 10 graybox auth, secret, and probe-safety hardening

Why:
- future backend work needs the durable invariant that API coverage must be non-vacuous, low-privilege principals must be real, and secret refs must fail closed.
@toderian toderian force-pushed the feat/redmesh-api-top10 branch from 9056597 to 1b5302d Compare May 13, 2026 20:47
toderian and others added 24 commits May 14, 2026 05:15
What changed:
- Added a runtime manifest for API Top 10 scenario scheduling and runner mapping.
- Passed launcher-assigned scenario IDs into probe contexts and gated API family runners before target I/O.
- Added manifest coverage, runner existence, and unassigned-scenario no-HTTP tests.

Why:
- Scenario slicing must skip unassigned work before requests are sent, not after findings are produced.
What changed:
- Added deterministic MIRROR and SLICE assignment planning for API scenario IDs.
- Stored assignment metadata on worker entries and overlaid each worker assignment into runtime JobConfig.
- Validated assignment hashes before target preflight and sized request budgets from assigned worker budgets.

Why:
- Distributed graybox scans need launcher-decided work splitting so workers never infer or execute unassigned API scenarios.
What changed:
- Moved runtime job-config secret resolution into the local launch error boundary.
- Added a shared helper for marking worker entries terminal with sanitized errors.
- Classified secret-resolution, assignment-validation, and launch failures with terminal reasons.

Why:
- A bad graybox secret_ref should surface as a terminal worker failure instead of escaping the launcher loop or leaving the job stuck.
What changed:
- Added worker-owned rollback journal records for stateful graybox mutations.
- Wrote pending records before mutate, updated records after revert, and surfaced manual cleanup on revert failure.
- Added attempted-unknown mutation handling and exempted cleanup/revert requests from probe budget exhaustion.

Why:
- Stateful API probes must leave a durable cleanup trail and attempt rollback even when the mutating request outcome is uncertain.
What changed:
- Require PT-OAPI2-03 to mint a disposable token from token_path before calling logout.
- Prove rollback by minting a fresh token after the logout test.
- Add an opt-in stateful helper mode for probes where verify=false is the clean outcome.

Why:
- Prevent the scanner from revoking the primary operator credential during logout invalidation checks.
What changed:
- Add launch-side positive-integer validation for request budgets and graybox target-config numeric safety fields.
- Normalize safe numeric strings before persistence and reject invalid, zero, negative, and oversized payload values.
- Make RequestBudget.consume reject non-positive consumption amounts and fail invalid assignment budgets closed.

Why:
- Scanner safety limits must be explicit launcher decisions, not silent coercions at worker runtime.
What changed:
- Add exact scalar placeholder rendering for API6 flow bodies using test_account, run_id, and job_id.
- Require {test_account} in API6 mutate/revert bodies unless an explicit unsafe static-body override is set.
- Keep API6 runtime probe state in local closures instead of mutating frozen ApiBusinessFlow config objects.

Why:
- Business-flow abuse probes must operate on designated test identities, not accidentally replay static real-user payloads.
What changed:
- require Bearer/API-key validation paths unless the launch explicitly opts into unverified auth
- gate unverified API scenarios to auth_unverified inconclusive findings and route API7 through scenario assignment checks
- carry configured API auth field names through finding storage, reporting, risk flattening, and LLM-boundary tests

Why:
- avoid misleading API Top 10 results and prevent custom auth secrets from leaking through direct finding/report paths
What changed:
- Send host-only target_confirmation in the API Top 10 e2e harness.
- Replace the skipped LLM-boundary placeholder with archive-backed redaction assertions.
- Add harness unit coverage for both contracts.

Why:
- Keep the e2e launch path aligned with authorization validation and make the LLM/report boundary check executable.
What changed:
- Mint honeypot bearer tokens in the API Top 10 e2e harness and launch through API-native auth.
- Retry transient status polling failures instead of aborting the harness.
- Align e2e fixture paths and manifest expectations with the API Top 10 honeypot routes.

Why:
- The honeypot form login is CSRF-protected; the e2e proof should exercise the bearer/API auth path used by the new probes.
Set the live API Top 10 e2e launch payload to request launcher-owned SLICE assignment, matching the multi-worker stateful validation contract. Align the e2e target config with probe contracts by using exact API6 test-account placeholders and authorizing root scope for the honeypot's exposed /openapi.json endpoint.

Verification: python -m py_compile extensions/business/cybersec/red_mesh/tests/e2e/api_top10_e2e.py; python -m json.tool extensions/business/cybersec/red_mesh/tests/e2e/fixtures/api_security_target_config.json; python extensions/business/cybersec/red_mesh/tests/e2e/api_top10_e2e.py --rm http://localhost:5082 --honeypot http://172.17.0.1:30001 --scenario vulnerable --timeout 600; python -m pytest extensions/business/cybersec/red_mesh/tests -q
GrayboxHttpClient.request() only changed method to GET on HTTP 303,
preserving POST on 301/302/307/308. Django's LoginView redirects
post-login with 302, so the wrapper re-POSTed the login form to the
LOGIN_REDIRECT_URL target. Django's CSRF middleware rejected it with
403 (csrftoken cookie rotates after successful login), and
_is_login_success saw status 403 and returned False. Result:
official_login_failed → FATAL abort, every form-auth graybox scan
aborted before reaching discovery.

Match real-world behavior of the requests library and browsers:
convert POST→GET on 301/302/303 (and drop request body); preserve
method on 307/308 per RFC 7231 §6.4.7. HEAD stays HEAD on any
redirect since it is safe + body-less.

Tests: ten new cases in test_http_client.py covering 301/302/303
conversion, 307 preservation, HEAD preservation, missing Location,
out-of-scope Location, 5-hop loop cap, and sticky GET after first
conversion in a chain.

Live verification: full graybox scan against the rm-gb honeypot
(job 3061a4a6) completes auth + discovery + all probes in 112s
(was aborting at 1s). 32 vulnerable findings vs 26 in baseline
4709a7e7, 7 stateful rollbacks vs 6, 0 regressions across all
24 PT-OAPI scenarios.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Webapp launches were failing with "Failed to store job config in R1FS"
whenever no operator-supplied secret-store key was configured. The
prior fail-closed gate required REDMESH_ALLOW_UNSAFE_SECRET_STORE_FALLBACK
plus a per-node fallback (cfg_comms_host_key or attestation private
key) — the per-node fallbacks differ across rm1 and rm2, so even when
the gate was opened the launcher's encryption key did not match the
worker's decryption key and credentials silently came back as empty
strings.

Add a built-in default secret-store key that ships with the plugin and
is therefore identical on every node running the same image. Resolution
order:
  1. REDMESH_SECRET_STORE_KEY env var (custom, audit unsafe=False)
  2. cfg_redmesh_secret_store_key plugin config (custom, unsafe=False)
  3. built-in default (unsafe=True; key_id "redmesh:default_plugin_key")

Persisted JobConfig still records secret_store_unsafe_fallback=true
when the default is in use, so audit trails reflect that the key is
well-known. The cross-node failure mode is gone; deployments that want
a real KMS-managed key just set the env var or plugin config.

Tests: replaced the obsolete fail-closed gate tests in
test_secret_isolation.py and test_api.py with assertions that the
default key produces correct metadata. Added TestSecretRoundTripAcrossNodes
which encrypts on a launcher FakeNode and decrypts on a separate worker
FakeNode through a shared in-memory R1FS — proves credentials survive
the persist→resolve round trip with no operator configuration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant