Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
46f2635
docs: ADR — scenario ID convention PT-OAPI<N>-<NN> for OWASP API Top 10
toderian May 12, 2026
d69a156
test(graybox): widen inventory regex to accept PT-OAPI<N>-<NN>
toderian May 12, 2026
faad18d
feat(graybox): add ApiSecurityConfig endpoint sub-models
toderian May 12, 2026
782412e
feat(graybox): wire api_security into GrayboxTargetConfig.from_dict
toderian May 12, 2026
1ac8a5a
test(graybox): cover ApiSecurityConfig round-trip and defaults
toderian May 12, 2026
bfb0691
feat(graybox): register v1 PT-OAPI scenario catalog entries
toderian May 12, 2026
b2b18f9
test(graybox): require ATT&CK mapping for v1 API findings
toderian May 12, 2026
ae02eb9
feat(graybox): scaffold ApiAccessProbes + register _graybox_api_access
toderian May 12, 2026
85e7e1a
feat(graybox): scaffold ApiAuthProbes + register _graybox_api_auth
toderian May 12, 2026
2bbb53a
feat(graybox): scaffold ApiDataProbes + register _graybox_api_data
toderian May 12, 2026
40a9fbb
feat(graybox): scaffold ApiConfigProbes + register _graybox_api_config
toderian May 12, 2026
511976b
feat(graybox): scaffold ApiAbuseProbes + register _graybox_api_abuse
toderian May 12, 2026
c48447e
test(graybox): cover all five family registries and dispatch
toderian May 12, 2026
7ca2909
feat(api): allow api_security target_config through launch_webapp_scan
toderian May 12, 2026
809d434
test(api): cover launch path preserves api_security payload
toderian May 12, 2026
2e255a9
docs: document api_security target_config JSON shape
toderian May 12, 2026
224b926
refactor(graybox): extract AuthStrategy ABC and move form-login into …
toderian May 12, 2026
e05590d
feat(graybox): Credentials value object with zeroising cleanup
toderian May 12, 2026
5a3e0c5
refactor(graybox): split AuthManager into strategy-dispatching orches…
toderian May 12, 2026
190ef69
feat(graybox): add auth descriptor sub-model (non-secret fields only)…
toderian May 12, 2026
370cd98
feat(graybox): make preflight strategy-aware
toderian May 12, 2026
0e4905e
feat(graybox): implement BearerAuth strategy (1.5a)
toderian May 12, 2026
baa2f20
feat(graybox): implement ApiKeyAuth strategy (1.5b)
toderian May 12, 2026
a657d15
feat(api): top-level bearer_token / api_key launch fields with archiv…
toderian May 12, 2026
482013d
test(graybox): cover all three auth strategies end-to-end
toderian May 12, 2026
b77ceb5
test(security): bearer_token / api_key never leak into archive, evide…
toderian May 12, 2026
530a60b
feat(graybox): add ProbeBase emit_vulnerable / emit_clean / emit_inco…
toderian May 12, 2026
aeb14b5
feat(graybox): centralised evidence scrubber in to_flat_finding
toderian May 12, 2026
4adb009
test(graybox): cover redaction patterns at storage boundary
toderian May 12, 2026
d75f67b
test(llm): extend LLM input isolation to API auth/cookie patterns
toderian May 12, 2026
8c8f5c9
feat(graybox): RequestBudget shared mutable budget object
toderian May 12, 2026
333894e
feat(graybox): wire RequestBudget into GrayboxProbeContext + worker s…
toderian May 12, 2026
2474bae
feat(graybox): ProbeBase.budget helper + max_total_requests config field
toderian May 12, 2026
9075aee
feat(api): request_budget launch param flows through to worker
toderian May 12, 2026
4c46353
feat(graybox): surface budget exhaustion metrics in worker outcome
toderian May 12, 2026
f2ebce8
test(graybox): RequestBudget exhaustion and shared-state semantics
toderian May 12, 2026
a464103
feat(graybox): StatefulProbeMixin enforces baseline-mutate-revert con…
toderian May 12, 2026
e7fde3c
feat(graybox): rollback_status field on GrayboxFinding
toderian May 12, 2026
7ddc969
test(graybox): cover stateful contract end-to-end
toderian May 12, 2026
4536552
fix(graybox): make _configured_secret_field_names defensive against n…
toderian May 12, 2026
b79dc8a
feat(graybox): implement PT-OAPI1-01 API BOLA probe
toderian May 12, 2026
67a7c07
feat(graybox): implement PT-OAPI5-01 + PT-OAPI5-02 BFLA read-only probes
toderian May 12, 2026
c5fb2a9
test(report): cover API probe-family findings flowing into flat findi…
toderian May 12, 2026
7b43d72
test(graybox): relax skeleton-dispatch assertion to accept real probe…
toderian May 12, 2026
8518a23
feat(graybox): implement PT-OAPI3-01 + PT-OAPI3-02 BOPLA probes
toderian May 13, 2026
a896fab
feat(graybox): implement API8 misconfig + API9 inventory probes
toderian May 13, 2026
9cdcb9b
feat(graybox): implement PT-OAPI2-01/02/03 API auth probes
toderian May 13, 2026
a730ff6
feat(graybox): extend PT-API7-01 SSRF probe to JSON body fields
toderian May 13, 2026
4043c98
feat(graybox): implement API4 + API6 abuse probes (Subphases 3.2 + 3.3)
toderian May 13, 2026
cf549f0
feat(graybox): implement PT-OAPI5-03 + PT-OAPI5-04 stateful BFLA
toderian May 13, 2026
35d1e7f
test(report): PT-A01-01 and PT-OAPI1-01 coexist on same asset (5.3)
toderian May 13, 2026
043b637
test(e2e): OWASP API Top 10 harness with manifest + target_config fix…
toderian May 13, 2026
f1eb85a
test(graybox): refine stateful-contract lint for post-Phase-3 reality
toderian May 13, 2026
de8c0f8
refactor: rename PT-OAPI5-04 → PT-OAPI5-02-mut per v1 plan
toderian May 13, 2026
3d0ee00
Revert "refactor: rename PT-OAPI5-04 → PT-OAPI5-02-mut per v1 plan"
toderian May 13, 2026
b592dbb
fix(graybox): preserve api-native auth credentials
toderian May 13, 2026
7db4b2a
fix(graybox): scrub configured secrets on probe errors
toderian May 13, 2026
62f4b3f
fix(graybox): require rollback for api6 flows
toderian May 13, 2026
adc508a
fix(graybox): separate method override from mutating bfla
toderian May 13, 2026
71cc522
fix(e2e): align api top10 stateful fixtures
toderian May 13, 2026
33e23f7
fix(graybox): api_data._render_url must prepend target_url
toderian May 13, 2026
7717446
fix(graybox): harden api auth launch contracts
toderian May 13, 2026
7f225d3
fix(graybox): make api probes non-vacuous and safer
toderian May 13, 2026
1b235ef
fix(e2e): unwrap api top10 scan responses
toderian May 13, 2026
5e8978d
fix(graybox): restore discovery budget extraction
toderian May 13, 2026
1b5302d
docs(redmesh): record api top10 graybox hardening
toderian May 13, 2026
5d73dce
fix(graybox): bind resolved secrets to job id
toderian May 14, 2026
2d6e908
fix(graybox): gate method override mutation
toderian May 14, 2026
83eb792
fix(graybox): bound api probe side effects
toderian May 14, 2026
3f2911f
docs(redmesh): record graybox api safety fixes
toderian May 14, 2026
b86ab54
chore: increment version
toderian May 14, 2026
50018dd
fix(graybox): canonicalize target config
toderian May 14, 2026
ef96111
feat(graybox): resolve target config secret refs
toderian May 14, 2026
823a9cd
fix(graybox): require dedicated secret keying
toderian May 14, 2026
40a3ea9
docs(redmesh): add security onion examples
toderian May 14, 2026
3e40aa1
fix(graybox): enforce scoped http requests
toderian May 14, 2026
c247dd8
feat(graybox): add runtime scenario assignment gates
toderian May 14, 2026
f279bfa
feat(graybox): add launcher-owned scenario assignments
toderian May 14, 2026
9b45789
fix(graybox): contain secret resolution failures
toderian May 14, 2026
ca3222a
fix(graybox): journal stateful rollback actions
toderian May 14, 2026
24f90bc
fix(graybox): require disposable logout tokens
toderian May 14, 2026
0254f76
fix(graybox): validate safety budgets at launch
toderian May 14, 2026
db8fd8e
fix(graybox): use test accounts in api abuse flows
toderian May 14, 2026
b2d6ea8
fix(graybox): fail closed for api auth and metadata
toderian May 14, 2026
3dff14e
test(graybox): harden api e2e harness
toderian May 14, 2026
74087c3
test(graybox): align api top10 e2e auth
toderian May 14, 2026
593ace9
test(graybox): run api e2e through sliced assignment
toderian May 14, 2026
248f42f
Merge remote-tracking branch 'origin/develop' into feat/redmesh-api-t…
toderian May 14, 2026
c8958c1
fix(graybox): convert POST→GET on 301/302 redirect like requests
toderian May 14, 2026
7cca305
fix(graybox): provide built-in plug-and-play secret-store key
toderian May 14, 2026
3397fd8
fix(graybox): require configured secret-store key for persisted crede…
toderian May 14, 2026
dd8c408
fix(graybox): harden api token validation
toderian May 14, 2026
f2ce8d3
fix(graybox): preserve rollback on uncertain stateful mutations
toderian May 14, 2026
cd4ed7e
fix(graybox): never let truthy verify return become a vulnerable finding
toderian May 14, 2026
a2917f8
fix(graybox): stabilize api scenario assignment metadata
toderian May 14, 2026
bc6a2da
fix(graybox): rehash worker assignment on reannounce
toderian May 14, 2026
5c7f73c
fix(graybox): legacy mirror compat for assignmentless webapp jobs
toderian May 14, 2026
2220dfc
fix(redmesh): merge worker terminal-error updates safely
toderian May 14, 2026
65e1705
Merge remote-tracking branch 'origin/develop' into feat/redmesh-api-t…
toderian May 15, 2026
563076c
chore: increment version
toderian May 15, 2026
c646849
fix(redmesh): honor unsafe-fallback opt-in regardless of REDMESH_ENV
toderian May 15, 2026
fd7690f
Merge remote-tracking branch 'origin/develop' into feat/redmesh-api-t…
toderian May 15, 2026
c75a619
chore: increment version
toderian May 15, 2026
c929cb6
chore(redmesh): restore dev secret fallback
toderian May 15, 2026
5942135
chore(redmesh): restore dev secret fallback
toderian May 15, 2026
4fede2a
Merge remote-tracking branch 'origin/feat/redmesh-api-top10' into fea…
toderian May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 0 additions & 79 deletions .devcontainer/rm1/devcontainer.json

This file was deleted.

188 changes: 188 additions & 0 deletions .devcontainer/watch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""
Development file watcher for Edge Node hot reload.

Watches for Python file changes in extensions/ and plugins/ directories,
then automatically restarts the edge node process.

Usage:
python .devcontainer/dev_watch.py

Options:
--no-initial Don't start the process immediately, wait for first change
--debounce N Seconds to wait before restarting (default: 1.0)
"""
import subprocess
import sys
import time
import os
import signal
import argparse
from pathlib import Path

try:
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
except ImportError:
print("Installing watchdog...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "watchdog", "-q"])
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler


class EdgeNodeReloader(PatternMatchingEventHandler):
"""Handles file changes and restarts the edge node process."""

def __init__(self, debounce_seconds=1.0):
super().__init__(
patterns=["*.py"],
ignore_patterns=["*/__pycache__/*", "*/.git/*", "*/_local_cache/*"],
ignore_directories=True,
case_sensitive=True,
)
self.process = None
self.last_restart = 0
self.debounce_seconds = debounce_seconds
self.restart_pending = False

def start_process(self):
"""Start or restart the edge node process."""
self.stop_process()

print("\n" + "=" * 60)
print(" Starting edge node...")
print("=" * 60 + "\n")

self.process = subprocess.Popen(
[sys.executable, "device.py"],
cwd="/edge_node",
preexec_fn=os.setsid,
)
self.last_restart = time.time()
self.restart_pending = False

def stop_process(self):
"""Stop the running edge node process and all its children."""
if self.process and self.process.poll() is None:
pgid = os.getpgid(self.process.pid)
print("\n Stopping edge node (PID: {}, PGID: {})...".format(self.process.pid, pgid))
os.killpg(pgid, signal.SIGTERM)
try:
self.process.wait(timeout=10)
except subprocess.TimeoutExpired:
print(" Force killing process group...")
os.killpg(pgid, signal.SIGKILL)
self.process.wait()
print(" Stopped.")

def _should_restart(self):
"""Check if enough time has passed since last restart."""
return time.time() - self.last_restart >= self.debounce_seconds

def _trigger_restart(self, event_path):
"""Handle a file change event."""
if not self._should_restart():
self.restart_pending = True
return

# Get relative path for cleaner output
try:
rel_path = Path(event_path).relative_to("/edge_node")
except ValueError:
rel_path = event_path

print("\n File changed: {}".format(rel_path))
self.start_process()

def on_modified(self, event):
self._trigger_restart(event.src_path)

def on_created(self, event):
self._trigger_restart(event.src_path)

def on_moved(self, event):
self._trigger_restart(event.dest_path)

def check_pending_restart(self):
"""Check and execute pending restart if debounce period passed."""
if self.restart_pending and self._should_restart():
print("\n Executing pending restart...")
self.start_process()


def main():
parser = argparse.ArgumentParser(description="Edge Node development watcher")
parser.add_argument("--no-initial", action="store_true", help="Don't start immediately")
parser.add_argument("--debounce", type=float, default=1.0, help="Debounce seconds")
args = parser.parse_args()

# Directories to watch
watch_dirs = [
"/edge_node/extensions",
"/edge_node/plugins",
]

# Also watch single files
watch_files = [
"/edge_node/constants.py",
"/edge_node/device.py",
]

handler = EdgeNodeReloader(debounce_seconds=args.debounce)
observer = Observer()

print("\n" + "=" * 60)
print(" Edge Node Development Watcher")
print("=" * 60)
print("\n Watching for changes in:")

for dir_path in watch_dirs:
path = Path(dir_path)
if path.exists():
observer.schedule(handler, str(path), recursive=True)
print(" - {}/**/*.py".format(path.name))

# Watch parent directory for single files
observer.schedule(handler, "/edge_node", recursive=False)
print(" - constants.py, device.py")

print("\n Press Ctrl+C to stop.\n")

# Handle graceful shutdown
def signal_handler(signum, frame):
print("\n\n Shutting down...")
handler.stop_process()
observer.stop()
sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

observer.start()

# Start the process initially unless --no-initial
if not args.no_initial:
handler.start_process()

# Main loop - check for pending restarts
try:
while True:
time.sleep(0.5)
handler.check_pending_restart()

# Check if process died unexpectedly
if handler.process and handler.process.poll() is not None:
exit_code = handler.process.returncode
if exit_code != 0:
print("\n Process exited with code {}. Waiting for file changes...".format(exit_code))
handler.process = None
except KeyboardInterrupt:
pass
finally:
handler.stop_process()
observer.stop()
observer.join()


if __name__ == "__main__":
main()
28 changes: 28 additions & 0 deletions docs/suricata-security-onion-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Suricata Security Onion Correlation Examples

Use RedMesh lifecycle events as an assessment window when correlating
Suricata alerts in Security Onion. The event payload includes a bounded
time window, authorization context, expected egress metadata, and report
references without exposing target IP values when redaction is enabled.

Example Security Onion query:

```text
event.dataset:suricata.eve
AND @timestamp >= window.started_at
AND @timestamp <= window.actual_end_at
AND redmesh.authorization_ref:*
```

Useful fields to preserve in analyst notes:

- `window.started_at`
- `window.actual_end_at`
- `window.grace_seconds`
- `window.clock_skew_seconds`
- `authorization_ref`
- `report_refs.pass_report_cid`

Treat matches as correlation context for the authorized RedMesh
assessment window. Keep rule tuning and alert handling in the normal SOC
workflow.
15 changes: 15 additions & 0 deletions extensions/business/cybersec/red_mesh/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,18 @@ Only append entries for critical or fundamental RedMesh backend changes, discove
- Change: added non-blocking SOC event hooks for launcher job-start events, pass completion, finding creation/triage, MISP export status, attestation status, and hard/terminal stop paths. Job/archive/stub models now preserve summary-only `soc_event_status`.
- Verification: `python -m pytest extensions/business/cybersec/red_mesh/tests/test_event_lifecycle_hooks.py extensions/business/cybersec/red_mesh/tests/test_misp_export.py extensions/business/cybersec/red_mesh/tests/test_state_machine.py extensions/business/cybersec/red_mesh/tests/test_finalization_aggregation.py extensions/business/cybersec/red_mesh/tests/test_api.py extensions/business/cybersec/red_mesh/tests/test_integration.py extensions/business/cybersec/red_mesh/tests/test_regressions.py extensions/business/cybersec/red_mesh/tests/test_repositories.py -q` passed with 224 tests; `python -m pytest extensions/business/cybersec/red_mesh/tests -q` passed with 1211 tests, 1 skipped, 3 warnings, and 6 subtests.
- Horizontal insight: lifecycle hooks should mutate only summary status and call isolated adapters through `services/event_hooks.py`; hook failures must degrade to SOC status/timeline metadata, never scan lifecycle exceptions.

### 2026-05-13T20:45:39Z

- Change: hardened OWASP API Top 10 graybox launch/runtime contracts: regular bearer/API-key credentials now flow through the existing encrypted `secret_ref` lane, secret refs fail closed when kind/storage/job ownership is invalid, API-native sessions validate with configured authenticated requests, and flat finding identity includes `scenario_id` plus endpoint evidence.
- Change: API probe families now emit explicit `INFO/inconclusive` findings for missing target inventory, require low-privilege sessions for BOLA/API6 checks, gate higher-risk API4/API8 probes behind operator opt-ins, and treat mutated-but-unverified stateful checks as inconclusive rather than clean.
- Verification: `python -m pytest extensions/business/cybersec/red_mesh/tests/test_secret_isolation.py extensions/business/cybersec/red_mesh/tests/test_api.py extensions/business/cybersec/red_mesh/tests/test_auth.py extensions/business/cybersec/red_mesh/tests/test_target_config.py extensions/business/cybersec/red_mesh/tests/test_graybox_finding.py extensions/business/cybersec/red_mesh/tests/test_stateful_contract.py extensions/business/cybersec/red_mesh/tests/test_probes_api_access.py extensions/business/cybersec/red_mesh/tests/test_probes_api_data.py extensions/business/cybersec/red_mesh/tests/test_probes_api_abuse.py extensions/business/cybersec/red_mesh/tests/test_probes_api_config.py extensions/business/cybersec/red_mesh/tests/test_probes_api_auth.py extensions/business/cybersec/red_mesh/tests/test_finalization_aggregation.py extensions/business/cybersec/red_mesh/tests/test_findings_redaction.py -q` passed with 302 tests and 10 subtests.
- Horizontal insight: API Top 10 graybox coverage is only meaningful when skipped scenarios are reported, low-privilege principals are real, and secret/runtime config boundaries line up from Navigator launch through worker resume and archive/report flattening.

### 2026-05-14T05:14:40Z

- Change: closed a secret-ref ownership gap in OWASP API Top 10 graybox worker startup by passing the expected job id explicitly into secret resolution before `JobConfig` coercion can drop non-archived fields.
- Change: moved `PT-OAPI5-03` method-override control traffic fully under the `run_stateful()` gate, so `allow_stateful_probes=false` prevents all mutating requests, and added specific inconclusive-reason callbacks for stateful probes that need attribution-preserving outcomes.
- Change: bounded API4 high-limit probing at an effective request limit of `1000` and changed API3 mass-assignment rollback to fail when the probe introduced a previously absent field instead of writing `false` and claiming rollback success.
- Verification: targeted API/graybox suite passed with `306 passed, 10 subtests`; broad `extensions/business/cybersec/red_mesh/tests -q` run passed `1461` tests and `36` subtests with one unrelated pre-existing failure for missing `docs/suricata-security-onion-examples.md`.
- Horizontal insight: RedMesh stateful probe safety must be enforced at the first target-mutating byte, not only around the vulnerability-attribution request; request-count budgets also need per-request work bounds for resource-consumption probes.
6 changes: 6 additions & 0 deletions extensions/business/cybersec/red_mesh/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ class ScanType(str, Enum):
{"key": "_graybox_misconfig", "cls": "misconfig.MisconfigProbes"},
{"key": "_graybox_injection", "cls": "injection.InjectionProbes"},
{"key": "_graybox_business_logic", "cls": "business_logic.BusinessLogicProbes"},
# OWASP API Top 10 2023 — five themed families (Subphase 1.3).
{"key": "_graybox_api_access", "cls": "api_access.ApiAccessProbes"},
{"key": "_graybox_api_auth", "cls": "api_auth.ApiAuthProbes"},
{"key": "_graybox_api_data", "cls": "api_data.ApiDataProbes"},
{"key": "_graybox_api_config", "cls": "api_config.ApiConfigProbes"},
{"key": "_graybox_api_abuse", "cls": "api_abuse.ApiAbuseProbes"},
]

# Graybox timing and limits
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ADR — Scenario ID convention `PT-OAPI<N>-<NN>` for OWASP API Top 10 2023

**Status**: Accepted
**Date**: 2026-05-12
**Context**: Graybox API Top 10 implementation, Subphase 1.0 (see `_todos/2026-05-12-graybox-api-top10-plan-detailed.md`).

## Decision

New OWASP API Top 10 (2023) graybox scenarios use the prefix **`PT-OAPI<N>-<NN>`** where:

- `<N>` is the OWASP API category number (1–6, 8, 9 for v1; API7 keeps its legacy ID, API10 is reserved for Phase 9).
- `<NN>` is a zero-padded sequence within the category (`01`, `02`, …).

Examples: `PT-OAPI1-01` (BOLA), `PT-OAPI3-02` (mass assignment), `PT-OAPI5-04` (mutating BFLA), `PT-OAPI9-01` (OpenAPI exposure).

**Out of scope of this ADR**: any scenario ID for API7 SSRF stays as the existing **`PT-API7-01`** for backward compatibility. Any scenario ID for API10 will be minted in Phase 9, not in v1.

## Context

The graybox catalog already uses `PT-A<NN>-<NN>` for OWASP Web Top 10 2021 scenarios (`PT-A01-01` … `PT-A07-06`). When adding OWASP API Top 10 (2023) coverage we considered several prefixes:

| Candidate | Pros | Cons |
|---|---|---|
| `PT-API<N>-<NN>` | Short, matches OWASP naming directly | One character away from `PT-A0<N>-<NN>` — pentesters reading reports under time pressure will misread. `PT-API1-01` vs `PT-A01-01` differ by one character in position 5. |
| `PT-API<N>:2023-<NN>` | Year-explicit | Punctuation in ID is hostile to grep, regex, CI test names, JSON keys. |
| `PT-OWASPAPI<N>-<NN>` | Fully unambiguous | Long. Inflates inventory tables and PDF columns. |
| **`PT-OAPI<N>-<NN>`** | Visually distinct from `PT-A` family. Short. OWASP-API mnemonic. | Slight learning curve (one-time). |

We chose `PT-OAPI<N>-<NN>`.

## Consequences

### Affected systems

1. **Backend catalog** — `extensions/business/cybersec/red_mesh/graybox/scenario_catalog.py` adds 23 new entries under the new prefix (see Subphase 1.2 in the plan).
2. **Inventory regex** — `extensions/business/cybersec/red_mesh/tests/test_detection_inventory.py` widens its scenario-ID matcher to accept `PT-OAPI\d{1,2}-\d+` alongside the existing `PT-A\d+-\d+` and `PT-API7-\d+`.
3. **Frontend (RedMesh-Navigator)** — `lib/domain/knowledge.ts::GRAYBOX_SCENARIOS` registers the new IDs; `OWASP_CATEGORIES` extends to include `API1`–`API9`; a shared `owaspCategoryKey()` helper replaces brittle `owasp_id.slice(0, 3)` usage so `API7:2023` resolves correctly.
4. **PDF report** — `lib/pdf/sections/vulnerabilityAssessment.ts` adds §3.3.3 "OWASP API Top 10" with `owaspCategoryKey(f.owasp_id)?.startsWith('API')` as the dispatch predicate. Legacy `PT-API7-01` MUST appear here.
5. **Operator docs** — `docs/guides/api-security-scanning.md` (Phase 8.6) explains how to read the new IDs.

### Backward compatibility

- `PT-A<NN>-<NN>` (Web Top 10 2021) IDs are unchanged.
- `PT-API7-01` (legacy SSRF) is preserved verbatim — never renamed. Frontend must continue to render it correctly.
- Detection-inventory floor counters are bumped by +23 (graybox floor 80 → ≥103) in Subphase 1.2.

### Non-decisions (out of scope of this ADR)

- Whether to deprecate the legacy `PT-A02-12` once `PT-OAPI2-01` is stable. Tracked as Phase 9 F12.
- ATT&CK / CWE / compliance-framework mapping schemes; tracked separately (Subphase 1.2 and Phase 9 F13).
- Whether `PT-API7-01` should eventually be renamed to `PT-OAPI7-01` for consistency. Not in v1; revisit when there is a separate need to migrate the legacy probe.

## References

- Plan: `_todos/2026-05-12-graybox-api-top10-plan-detailed.md` (Subphase 1.0, lines 253–280; Subphase 1.2 ID table, lines 315–329).
- OWASP API Security Top 10 2023: https://owasp.org/API-Security/editions/2023/en/0x11-t10/
- Existing OWASP Web Top 10 2021 scenarios: `extensions/business/cybersec/red_mesh/graybox/scenario_catalog.py`.
Loading