A governed agentic orchestrator for reverse engineering and authorized penetration testing. A supervisor decomposes an objective; reverse-engineering, recon, and exploitation specialists do the work through real MCP tool integrations — and every action that can touch another system is held behind a scope gate, default-deny approval, and a tamper-evident audit log.
It is the offensive-security sibling of a safety-first agent design: the whole point is answering "what is this agent allowed to do?" before it does anything.
⚠️ Authorized use only. This software is for security professionals operating under explicit written authorization (a signed engagement, your own lab, or a CTF you are entered in). Scanning, probing, or exploiting systems you do not own or lack permission to test is illegal in most jurisdictions. The engagement-scope gate fails closed by design; do not remove it. You are responsible for your use.
The governed loop end to end — the agent proposes a recon action, the scope gate and default-deny hold it for a human, a reviewer approves, and only then does it execute (scope re-checked), with the decision committed to a verifiable audit chain. No real attacks: the bundled reference server returns simulated output.
Reproduce it yourself (no API keys, no external tools):
make dev && python examples/demo.py- Real MCP integration. A dependency-free MCP client speaks JSON-RPC 2.0 over
stdio (
initialize→tools/list→tools/call). It is protocol-compatible with standard MCP servers (radare2, Ghidra, nmap, …); the integration is validated end-to-end against the bundled reference server, and wiring a specific third-party server needs the operator setup in Wiring real MCP servers. The reference server keeps the whole path runnable in CI with zero external tools. - The agent proposes; a human disposes. Reverse engineering is read-only and runs directly. Recon and exploitation specialists propose a tool call and stop. Nothing offensive executes until a human approves it.
- Scope / rules-of-engagement gate. Active tools may only touch targets on an allow-list (hosts, CIDRs, wildcard domains). Empty scope authorizes nothing.
- Default-deny tools. Every active (recon/web/exploit) MCP tool is side-effecting and gated; only after approval — and a re-checked scope — does it run.
- Tamper-evident audit. Each decision is committed to a SHA-256 hash chain that
verify()can validate, so the record of who approved what can't be quietly rewritten.
flowchart TD
O["Engagement objective"] --> S["Supervisor: decompose"]
S --> D{"Dispatch: next subtask?"}
D -->|re| RE["RE specialist<br/>read-only, runs now"]
D -->|recon / exploit| AC["Active specialist<br/>PROPOSES a tool call"]
D -->|analysis / reporting| SY["Synthesize"]
RE --> R["Reviewer"]
AC --> R
SY --> R
R -->|proposed active action| SG{"Scope gate<br/>(target in ROE?)"}
SG -->|in scope| Q[("Approval queue")]
SG -->|out of scope| X["Refused"]
Q --> H{{"Human: approve / reject / edit / take_over"}}
H -->|approved| EX["execute_tool<br/>scope re-checked, MCP call"]
H --> AU["Audit hash chain"]
EX --> AU
R -->|accept| D
D -->|done| RP["Reduce: engagement summary"]
subgraph governed [Human-governed boundary]
SG
H
AU
end
classDef ctrl fill:#eef,stroke:#446;
class SG,H,AU,R ctrl;
Requires Python 3.10+. Runs with no API keys and no external tools (uses the mock LLM provider and the bundled reference MCP server).
make dev # pip install -e ".[dev]"
# Authorize a scope, then run an objective. Active actions are proposed + queued.
OFFSEC_SCOPE_TARGETS="scanme.example.com" \
python -m offsec.main "Recon the host, reverse the sample strings, and write a report"
python -m offsec.main --list-tools # connect MCP servers and list registered toolsStatus: the bundled reference server is the only integration verified end-to-end. The third-party servers below are illustrative — the project speaks standard MCP, but exact package names, install steps, command binaries, and tool schemas vary per server and are not verified here. Treat this as the integration shape, not copy-paste config.
Declare servers in OFFSEC_MCP_SERVERS (JSON). Each is launched over stdio and its
advertised tools are imported into the registry.
# ILLUSTRATIVE ONLY — replace the <placeholders> with a real MCP server you have
# installed and are authorized to use. These command names are not shipped tools.
export OFFSEC_MCP_SERVERS='[
{"name":"radare2","category":"re","command":"<radare2-mcp-binary>","args":[]},
{"name":"nmap","category":"recon","command":"<nmap-mcp-binary>","args":[]}
]'
export OFFSEC_SCOPE_TARGETS="10.10.10.0/24,*.lab.internal"To make a real server work today, mind these known gaps:
- Set
categoryper server for accuracy. Auto-categorization keys off tool-name prefixes (re_/recon_/web_/exploit_). Anything unrecognized is fail-safe gated — treated as an active, scope-gated, approval-required tool and logged — so an unclassified tool can never run unreviewed. Setcategoryso risk levels and routing are correct (and so genuinely read-only tools aren't over-gated). - Arguments are mapped from each tool's
inputSchema. The engagement target is placed into whatever the tool names its target parameter (target/url/host/rhost/ …), and other required parameters are filled with type-appropriate placeholders for the reviewer to adjust. Semantic understanding of non-target parameters is still up to the human at approval time. - RE tools run read-only and directly; recon / exploit tools are proposed,
scope-checked, and require human approval before
offsec.mcp.manager.execute_toolruns them. - No sandboxing. A configured server runs as a local subprocess with your privileges — run untrusted servers in a container/VM.
In short: the orchestration, scope gate, approval flow, audit chain, and MCP client are real and working against the reference server. Turning on real offensive capability is operator setup plus the integration work above — this is a security-architecture portfolio project, not a turnkey offensive tool.
| Variable | Default | Purpose |
|---|---|---|
OFFSEC_SCOPE_TARGETS |
— | Allow-list of authorized targets (hosts/CIDRs/*.dom). Empty = nothing in scope |
OFFSEC_RE_ARTIFACT_PATH |
— | Local artifact the RE specialist may analyze |
OFFSEC_MCP_SERVERS |
— | JSON array of MCP server declarations |
OFFSEC_MCP_ENABLE_REFERENCE_SERVER |
true |
Launch the bundled safe reference server |
OFFSEC_REVIEW_AUTH_ENABLED |
true |
Require authenticated reviewers (secure by default) |
OFFSEC_REVIEW_USERS |
— | name:token pairs for reviewer auth |
OFFSEC_REVIEW_SESSION_TIMEOUT_MINUTES |
15 |
Reviewer session lifetime (0 disables) |
OFFSEC_DB_PATH |
— | SQLite file for durable queue + audit. Empty = in-memory |
OFFSEC_AUDIT_HMAC_KEY |
— | HMAC-sign the audit chain (integrity-protected). Empty = SHA-256 (integrity-evident) |
OFFSEC_LOG_LEVEL |
INFO |
Log verbosity for security events |
OFFSEC_DEFAULT_LLM_PROVIDER |
mock |
openai | anthropic | mock |
Framed against the OWASP Top 10 for LLM Applications and agentic-AI failure modes:
- Excessive agency (the headline risk). The agent cannot act on a target by itself. Active tools are default-deny, scope-gated, and require human approval; exploitation always requires a human take-over, never one-click approval.
- Prompt injection. A poisoned binary string or HTTP banner can influence what the model proposes, but not what executes — proposal and execution are separated by the scope gate and a human.
- Out-of-scope action. The ROE gate fails closed and is re-checked at execution time, so neither a model mistake nor an approved-but-mistyped target can stray.
- Weak auditability. Every decision is hash-chained (
timestamp, task_id, action, decision, approver, target, ...) and independently verifiable. WithOFFSEC_AUDIT_HMAC_KEYset, records are HMAC-signed — integrity-protected, not merely integrity-evident — and persisted to SQLite whenOFFSEC_DB_PATHis set. - Insecure tool use. Tools are schema'd, rate-limited, and categorized by risk; unknown / unclassified tools fail safe to active + scope-gated + approval-required.
Beyond the zero-config demo, the operational pieces are wired:
export OFFSEC_DB_PATH=/var/lib/offsec/offsec.db # durable queue + audit (SQLite)
export OFFSEC_AUDIT_HMAC_KEY="$(openssl rand -hex 32)" # integrity-protected audit
export OFFSEC_REVIEW_AUTH_ENABLED=true
export OFFSEC_REVIEW_USERS="alice:$(openssl rand -hex 16)"
export OFFSEC_SCOPE_TARGETS="10.10.10.0/24"
export OFFSEC_LOG_LEVEL=INFO
python -m offsec.main "…"A Dockerfile is included (docker build -t offsec-orchestrator .) — run it with a
mounted volume for the SQLite store and the env above.
Honest remaining limitations of this portfolio / research project:
- The bundled reference server simulates recon/web/exploit output. Real offensive capability comes only from MCP servers you configure and authorize.
- Audit integrity depends on key custody. HMAC signing protects the chain, but a WORM/append-only sink and periodic signed anchoring would harden it further.
- Reviewer auth is shared-token; production needs SSO/OIDC, rotation, revocation.
- No sandboxing of MCP servers — they run as local subprocesses with your privileges. Run untrusted servers in a container/VM.
- Argument mapping is schema-driven but not semantic — it maps the target and fills required params by type; it won't infer meaningful values for tool-specific options, so a human still tunes non-target arguments at approval.
- Approvals are out-of-band (queue +
execute_tool); no live mid-graph interrupt/resume, and the SQLite store isn't yet hardened for concurrent multi-operator writes.
Released under the MIT License. Use responsibly and legally.