An agentic, workflow-driven pentesting CLI. Claude Code-style architecture for authorized offensive security.
Project description
PentestCode
An agentic, workflow-driven pentesting CLI. Claude Code-style architecture — tool modules, permission modes, subagents, dynamic workflows, conversation compaction, and agent teams — rebuilt from scratch for authorized offensive security, on top of a Docker sandbox and a LiteLLM brain that talks to any model.
Authorized testing only. PentestCode is built for security professionals testing systems they own or have explicit written permission to test. Like Metasploit or nuclei, the tool does not police your targets — you are responsible for having authorization for anything you point it at.
Why not just Kali + scripts?
Kali gives you tools; PentestCode gives you an operator. It:
- Remembers the environment. All engagement state lives in plain markdown
(
.pentestcode/), so it never re-runs recon it already did and any session can resume mid-engagement — even after the conversation is compacted. - Fans out work as workflows. Describe a task and it writes an orchestration script that spawns dozens of focused subagents in parallel (recon per host, enum per service, a scanner per target) — then an adversarial verifier re-tests every candidate finding before it's allowed into the report.
- Runs everything in a sandbox. Every command executes inside a Kali Docker container, never on your host.
- Streams like Claude Code. Assistant text streams token-by-token into the
TUI, tool calls render as live rows with elapsed time, long scans run on a
worker thread so the interface never freezes, and Esc interrupts a run
mid-turn. Commands are wrapped in the container's
timeoutbinary so a hung scan can't stall the session. - Compacts long engagements. Auto-summarizes the conversation as it nears the context window, anchored to the memory files so nothing real is lost.
- Zero setup. No init, no scope ceremony —
pentestcode chat, type a target, go. Just like Claude Code.
The architecture
you ──> CLI (Typer) ──> Agent loop ──> LiteLLM ──> any model (Claude/GPT/local)
│ │
│ ├─> Tools ──> PermissionGate ──> Docker sandbox (Kali)
│ │
│ ├─> MemoryStore (.pentestcode/*.md — env memory)
│ ├─> CompactionManager (auto /compact)
│ ├─> Subagents (isolated contexts)
│ ├─> WorkflowRuntime (agent()/pipeline(), checkpoints)
│ │ └─> Adversarial Verifier
│ └─> TeamManager (shared tasks + mailboxes)
│
└─> ReportGenerator ──> report.md + report.html
Install
Requires Python 3.9+, Docker running, and one model key (ANTHROPIC_API_KEY,
OPENAI_API_KEY, or an Ollama/LM Studio endpoint).
From PyPI (one command):
pip install "pentestcode[tui]" # [tui] = the terminal UI
Straight from GitHub (no clone):
pip install "pentestcode[tui] @ git+https://github.com/Arpitpm23/pentestcode.git"
From a local clone (recommended if you'll edit the code):
git clone https://github.com/Arpitpm23/pentestcode.git
cd pentestcode
pip install -e ".[tui]" # -e = editable, .[tui] = this repo + the TUI
Use either
pip installorpipx install— not both.pipxis just pip in an isolated venv (nice for CLI tools); swappip install→pipx installin any command above if you prefer it.
Then verify:
pentestcode doctor # checks docker + API keys
Quickstart
No init, no scope file required. Just open the chat and go:
pentestcode chat
# (optional, once: build the Kali sandbox image)
pentestcode init
Inside chat — targets come straight from your messages:
> recon scanme.nmap.org # just say what to hit
> ultracode: audit dvwa.local end to end # model writes its own workflow
> /workflow vuln_audit 10.0.0.5 # scan + adversarially verify
> /context # token usage before compaction
> /compact # fold the conversation now
> /report # md + html report
Want to constrain it to a scope? Write .pentestcode/SCOPE.md (or run
pentestcode init --targets a.com,b.com) and commands are checked against it.
No SCOPE.md → runs unrestricted, like any CLI tool.
Dynamic workflows
The centerpiece. Say ultracode: <task> (or /workflow <name>) and the model
writes a Python orchestration script using two primitives:
result = await agent(prompt, schema=None, label=None, tools=None, model=None)
results = await pipeline(items, fn, concurrency=None)
The script runs in a restricted runtime — no filesystem/shell/imports; only
its subagents can act. The plan lives in script variables, not the model's
context; intermediate results never touch the main conversation; every agent is
checkpointed to .pentestcode/workflows/<run_id>/ so an interrupted run resumes
from cache. Scripts you like can be saved to .pentestcode/workflows/ and re-run as
/workflow <name>.
Bundled workflows: recon_sweep, service_enum, vuln_audit,
exploit_verify, full_engagement.
Adversarial verification
Scanners lie. Every candidate finding a workflow produces is handed to a
separate verifier subagent whose job is to refute it — re-test the target,
hunt for false positives. Only confirmed findings are written to
FINDINGS.md and into the report. This is the difference between a 40-item
nuclei dump and a 6-item report you can defend.
Environment memory
.pentestcode/ is the agent's long-term memory, in human-editable markdown:
| File | What it holds |
|---|---|
SCOPE.md |
authorized targets, constraints, authorization |
STATE.md |
hosts, services, credentials, current phase, done-list, open threads |
FINDINGS.md |
verified findings only |
WORKLOG.md |
append-only log of every command + outcome |
Read at session start, injected again after compaction, updated the moment the agent learns anything. Diff it in git, edit it by hand, the agent adapts.
Agent teams
For work that needs parallel collaboration (not just fan-out):
pentestcode teams "full audit of the web tier" --members recon,web,network
A lead session spawns independent teammates — own context, own compaction — coordinating through a shared, file-locked task list and JSON mailboxes, with plan-approval gates before risky stages.
Safety model
PentestCode takes the same posture as Metasploit, nuclei, or sqlmap: the tool doesn't police your targets — you do. It runs unrestricted by default and acts on whatever you give it in chat. Two things are on you:
- Have authorization for every target. This is an authorized-testing tool.
- The Docker sandbox isolates commands from your host — it does not make an unauthorized target authorized.
Optional friction you can turn on:
- Scope guidance: write
.pentestcode/SCOPE.md(orpentestcode init --targets a.com,b.com) and the agent is told to stay inside it (the list is injected into its instructions and read back on demand). This is guidance, not a hard network boundary — the sandbox doesn't block egress. Delete the file to run unrestricted.
The TUI
The default interface is a full-screen terminal UI (built on prompt_toolkit,
the same class of framework Claude Code's Ink renderer is). It streams the
model's text token-by-token, renders each tool call as a live row with elapsed
time, keeps the interface responsive during long scans (docker exec runs on a
worker thread), and lets you Esc to interrupt a run mid-turn. Commands are
wrapped in the container's timeout binary so a hung scan can't stall the
session. Input typed while the agent is busy is queued, not dropped.
Permission modes
Every command runs inside the Docker sandbox — nothing ever executes on your
host (not even ls). The container is the only execution path and can only
reach the engagement workspace volume. So permissions are about friction, not
isolation. Five modes, cycled with shift+tab:
| Mode | Behavior |
|---|---|
default |
Ask before any command not covered by an allow rule. Approval dialog: ↑/↓ navigate, enter/y allow, a always, n/esc deny. |
acceptEdits |
Auto-approve file edits in the engagement workspace; still ask for bash. |
plan |
Read-only research mode. bash/writes are denied outright; grep/glob/read run free. |
bypassPermissions |
Run everything without asking. Safe because the command is confined to the container. |
dontAsk |
Every prompt becomes a denial (headless-safe). |
Switch with /mode <name> or cycle with shift+tab. Persistent rules live in
settings.yaml under permissions.rules (allow/ask/deny lists, e.g.
"bash", "bash:nmap *", "file_write"); a session-scoped always choice
remembers a base command until you exit.
Hooks
Run your own shell commands on agent lifecycle events, from settings.yaml:
hooks:
PreToolUse:
- matcher: "bash"
hooks:
- type: command
command: "echo \"about to run: $TOOL_NAME\" >> /tmp/audit.log"
PostToolUse:
- matcher: "bash|file_write"
hooks:
- type: command
command: "./scripts/notify.sh"
timeout: 10
Events: PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit,
SessionStart, SessionEnd, Stop, PreCompact, PostCompact,
SubagentStart, SubagentStop. A hook gets JSON on stdin; exit code 2
blocks the tool (stderr goes back to the model), JSON stdout can rewrite input
or inject context. Matchers are exact, pipe-lists (bash|file_write), or regex.
Configuration
~/.pentestcode/settings.yaml (user) and .pentestcode/settings.yaml (project).
Every Config field is settable there; env override: PENTESTCODE_MODEL.
model:
provider_model: claude-sonnet-4-6 # any LiteLLM model string
subagent_model: gpt-5-mini # cheaper model for workers
effort: high # low|medium|high|ultracode
permissions:
mode: default # default|acceptEdits|plan|bypassPermissions|dontAsk
rules:
allow: ["bash:nmap *", "file_read"]
deny: []
workflows:
max_concurrent_agents: 16
size_guideline: medium # unrestricted|small|medium|large
Sessions
Conversations persist to .pentestcode/sessions/<id>.jsonl. Resume with
pentestcode chat --continue (latest), pentestcode chat --resume <id>, or
/sessions + /resume <id> inside chat.
Community plugins
Drop your own workflows in .pentestcode/workflows/<name>.py — same agent() /
pipeline() primitives — and they appear next to the bundled ones. Custom slash
commands go in .pentestcode/commands/<name>.md (with YAML frontmatter for
description/argument-hint/allowed-tools); skills in .pentestcode/skills/<name>/SKILL.md.
See docs/plugins.md.
Development
pip install -e ".[dev]"
pytest tests/ # memory, gate (5 modes), workflow runtime, streaming, interrupt
Contributions welcome — see CONTRIBUTING.md.
License
Apache 2.0 — see LICENSE.
Architecture inspired by Anthropic's Claude Code (tool modules, permission modes, subagents, hooks, dynamic workflows, compaction). Written from scratch; no Claude Code source is used or distributed.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pentestcode-0.3.0.tar.gz.
File metadata
- Download URL: pentestcode-0.3.0.tar.gz
- Upload date:
- Size: 89.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d175e614f94099fe7c850735e100a84289d107bb876b360f99204a754d3a9d6
|
|
| MD5 |
809445edbf4e89c22344b580948a5d9c
|
|
| BLAKE2b-256 |
7297bc3834436f72858fc5d01edb15d3f2b4bc11aeb6ba815de183dbc8c1f7f9
|
File details
Details for the file pentestcode-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pentestcode-0.3.0-py3-none-any.whl
- Upload date:
- Size: 98.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a5959c8516bd2eacf2001cfc2b3e0168d0601d7eed8238e969c3f7925f9254b
|
|
| MD5 |
fc5c674fe10cf8aa711762ef7158bb63
|
|
| BLAKE2b-256 |
06a04c7b586bab9def62a61a6fce5d06f6c73be6c8cc6aa7c64a10015b58678a
|