Skip to main content

An agentic, workflow-driven pentesting CLI. Claude Code-style architecture for authorized offensive security.

Project description

PentestCode

PyPI Python License

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 timeout binary 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 install or pipx install — not both. pipx is just pip in an isolated venv (nice for CLI tools); swap pip installpipx install in 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 (or pentestcode 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pentestcode-0.3.0.tar.gz (89.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pentestcode-0.3.0-py3-none-any.whl (98.1 kB view details)

Uploaded Python 3

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

Hashes for pentestcode-0.3.0.tar.gz
Algorithm Hash digest
SHA256 7d175e614f94099fe7c850735e100a84289d107bb876b360f99204a754d3a9d6
MD5 809445edbf4e89c22344b580948a5d9c
BLAKE2b-256 7297bc3834436f72858fc5d01edb15d3f2b4bc11aeb6ba815de183dbc8c1f7f9

See more details on using hashes here.

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

Hashes for pentestcode-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5a5959c8516bd2eacf2001cfc2b3e0168d0601d7eed8238e969c3f7925f9254b
MD5 fc5c674fe10cf8aa711762ef7158bb63
BLAKE2b-256 06a04c7b586bab9def62a61a6fce5d06f6c73be6c8cc6aa7c64a10015b58678a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page