Exploit Agent — Policy (policy.py)
Permission + budgets + tamper-evident audit. 589 lines.
Verified symbols
| Symbol | Kind | Line | Notes |
|---|---|---|---|
ExploitPermission | Enum | 13 | FULL_ACCESS/APPROVE_ONLY/READ_ONLY |
ExploitSettings | dataclass | 24 | Budgets + flags; effective_max_* props |
ExploitRecord | dataclass | 143 | prev_hash/hash chain |
ExploitPolicy | class | 277 | approve_action, record, can_proceed, is_attack_mode |
_TOOL_ACTION_CATEGORY | dict | 92 | Tool → ScopeGate category |
_record_chain_hash | def | 192 | Canonical JSON sha256 excluding hash |
verify_audit_chain | def | 205 | Startup verifier; skips MCP unchained rows |
EXPLOIT_AUDIT_FILENAME | const | 129 | exploit_audit.jsonl |
MAX_INMEMORY_AUDIT_RECORDS | const | 139 | 500 ring buffer |
ExploitPermission (policy.py:13)
| Value | String | Behavior |
|---|---|---|
FULL_ACCESS | full_access | Auto-approve after mission-scope check (_enforce_mission_scope); no command-content inspection |
APPROVE_ONLY | approve_only | Every action prompts operator (ALLOW <host>) |
READ_ONLY | read_only | Propose-only (status="proposed", returns False) |
Resolution: tools/cli_exploit_settings.py:_resolve_exploit_permission – missing/unknown key → READ_ONLY. Recon always READ_ONLY (cli_exploit_settings.py:157).
ExploitSettings (policy.py:24)
| Field | Default | Notes |
|---|---|---|
permission | APPROVE_ONLY | |
attack_mode | false | Enables raised budgets |
max_commands_per_session | 50 | Pre-attack |
max_rounds | 30 | Pre-attack |
attack_max_commands | 150 | When attack_mode |
attack_max_rounds | 50 | |
attack_max_duration_minutes | 360 | Time budget |
long_session_enabled / persist_messages | false | |
context_summarize_every | 10 | Compaction gap |
max_pivot_depth | 2 | Unused in policy; enforced in orchestrator |
enforce_rate_limit | true | ScopeGate rate limiter |
adaptive_exploits_enabled / max_mutations | false / 5 | |
outcome_judgment_flow_a | false | Opt-in |
Props: preapproved (policy.py:80), effective_max_commands (:84), effective_max_rounds (:87).
ExploitPolicy (policy.py:277)
Constructor binds workspace, prompt_func, approval_provider, scope_gate, loads exploit_audit.jsonl tail into _last_hash (_load_last_hash at policy.py:510), verifies chain (verify_audit_chain), sets _locked_ip / _allowed_targets.
| Member | Line | Description |
|---|---|---|
approve_action(action, command, detail) | 368 | Returns True if approved |
record(action, command, ...) | 477 | Appends ExploitRecord + hash + JSONL |
can_proceed | 360 | command_count < effective_max_commands |
is_full_access / is_attack_mode | 356 | Permission checks |
read_audit_records() | 540 | Full on-disk read (authoritative) |
_load_last_hash() | 510 | Seeds chain from existing log |
_write_record() | 535 | Append JSONL |
approve_action flow (policy.py:373):
attack_modewithoutFULL_ACCESS→SECURITY_EVENTwarning!can_proceed→status="denied"row,FalseREAD_ONLY→status="proposed",FalseFULL_ACCESS→_enforce_mission_scopeconsultsscope_gate(forbidden categories + asset rules;None= permissive); on deny →status="SCOPE_DENIED"row,False; else increment_command_count,True(no command-content inspection)APPROVE_ONLY→ delegate toapproval_providerorprompt_func(ALLOW <host>); every denial/abort/budget refusal writes astatus="denied"row (_record_denial)
Audit: ring buffer MAX_INMEMORY_AUDIT_RECORDS=500 (policy.py:505); disk JSONL is authoritative.
Hash chain
_record_chain_hash (policy.py:192) = sha256(sorted_json_without_hash). prev_hash links to prior record's hash. verify_audit_chain skips MCP rows without hash (policy.py:237).
_TOOL_ACTION_CATEGORY (policy.py:102)
Maps concrete MCP tools (dump_credentials, kerberoast, lateral_exec, generate_payload, run_exploit_terminal, msf*) to ScopeGate categories (credential_dumping, lateral_movement, payload_generation, exploit_execution). Category names intentionally absent from scope_gate._HARD_FORBIDDEN_SUBSTRINGS so full_access keeps them available; operator opts out via exploit.forbidden_actions.
Config keys
| Key | Effect |
|---|---|
exploit.permission | Mode |
exploit.mode | Standalone etc. |
exploit.max_commands_per_session / max_rounds / attack_max_* | Budgets |
exploit.forbidden_actions | Category deny via _TOOL_ACTION_CATEGORY |
exploit.allowed_targets / require_explicit_allowlist | Allowlist (checked in MCP layer, not here) |
exploit.long_session.* | long_session_enabled |
exploit.adaptive_exploits_* | Mutator |
outcome_judgment.flow_a | Judge bridge |
Tests
| File | Verified | Covers |
|---|---|---|
tests/test_exploit_permission.py | yes | Permission matrix |
tests/test_exploit_scope_gate.py | yes | Mission-gate enforcement (SCOPE_DENIED) + rate limit via policy |
tests/test_approval_denial_audit.py | yes | Denial rows + chain integrity for every deny exit |
tests/test_exploit_action_category.py | yes | _TOOL_ACTION_CATEGORY |
tests/test_audit_chain.py | yes | Hash chain + tamper detection |
tests/test_audit_memory_bound.py | yes | 500 ring buffer |
tests/test_audit_redaction.py | yes | Redaction in MCP audit rows |
tests/test_tool_outcome_tracker.py | yes | _ToolOutcomeTracker |