Skip to main content

pwner

An agentic network pentest tool you talk to.

CI status License: AGPL-3.0-or-later Python 3.10+

English  ·  Español  ·  Deutsch  ·  日本語  ·  Türkçe

Safety model  ·  Install  ·  First run  ·  Your model  ·  PCI DSS  ·  Documentation

A pwner session: the operator sets a target and asks for a full scan. The agent port-scans, fingerprints services, checks for known issues, reports two findings, then stops and asks permission before brute-forcing SSH. The operator answers n and it skips.

Example session against a fictional lab host. The agent's wording and the counts are illustrative, not a recording of a live run. Note the last four lines: it asks before anything intrusive, and a refusal stops it.

pwner is an LLM-driven agent that plans and runs a network penetration test from your terminal. It discovers hosts, fingerprints services, checks them against known issues, and — when the target warrants it and you approve — exploits them to prove impact. It drives real tools (nmap, nuclei, netexec, impacket, hydra and others) rather than simulating them, and it reports confirmed findings with severity, evidence and a fix.

It runs on your own model, or on no model at all.

  • You talk to it. Not a flag soup: say "scan it, focus on the database" and it decides what to run, shows you its reasoning as it goes, and you can redirect it mid-run.
  • Scope is enforced in code. Every tool call is checked against the target you named before it runs — not asked of the model in a prompt. See Safety model.
  • Intrusive actions are denied by default. Brute-force, remote command execution, credential dumping and reverse shells need your explicit approval.
  • Your model, your machine. OpenAI, Anthropic, Google, DeepSeek, or a server on your own hardware — Ollama, Lemonade, llama.cpp, anything OpenAI-compatible. A local endpoint needs no API key. Nothing is sent anywhere until you configure one.
  • Works with no model. --no-ai runs the full discovery → fingerprint → detect pipeline deterministically, with no API key and nothing leaving the machine.
  • 52 tools across nmap, HTTP, web crawling and parameter probing, per-service probes, Active Directory and credential work, each a thin wrapper over the standard binary rather than a reimplementation.

Safety model

Two mechanisms, both in the tool layer, both outside the model's reach. The model can ask for anything; it cannot widen its own authorization.

flowchart LR
    OP(["operator"]) -->|"plain language"| AG["agent loop"]
    AG -->|"tool call"| SG{"scope guard"}
    SG -->|"host outside<br>the target"| R1["refused"]
    SG -->|"in scope"| CG{"consent gate"}
    CG -->|"intrusive,<br>not approved"| R2["skipped"]
    CG -->|"approved, or<br>not intrusive"| RUN["runs against<br>the target"]
    classDef guard fill:#ffb000,stroke:#8a5f00,color:#140d00,stroke-width:2px
    classDef stop fill:none,stroke:#8b949e,stroke-dasharray:4 4
    class SG,CG guard
    class R1,R2 stop

A refusal is not an error that stops the run: it goes back to the model as a tool result, so the agent learns it overstepped and carries on inside the boundary.

Scope lock. build_dispatch wraps every tool so each host-carrying argument is checked against your target before the tool body runs. Tools are in-scope-by-default: one that forgets its own check is still guarded, and an unregistered tool name fails closed. A network argument has to sit entirely inside the authorization, so a /24 scope cannot be widened to a /16 by a tool argument.

Changing the scope. The one thing the guard cannot check is set_target, because its argument is the new scope, so there is nothing to check it against. It carries its own gate instead: narrowing, or returning to something you already authorized, is free, and moving anywhere else needs your answer every time. An earlier approval for some other action does not buy it, --auto-consent does not cover it, and a non-interactive run refuses it outright.

Consent gate. Anything active is denied unless you approve it in that session. The agent cannot self-approve — consent is resolved outside the model — and a non-interactive run refuses it unless you launched it with --auto-consent.

These two are the part of the codebase under the most test: 77 tests on the scope boundary in tests/test_scope.py, 32 more on the scope-change gate in tests/test_scope_change.py, and 13 on the consent gate in tests/test_consent.py, out of 725 overall. If you are going to read one thing before pointing this at a network, read those.

Intended use:

  • Authorized penetration tests, with written permission and an agreed scope
  • Bug-bounty work, inside the program's stated rules
  • Your own lab, your own hardware, CTFs
  • Security research on systems you own

Not this tool's purpose, and not defensible:

  • Anything on a host you do not own and have no written permission to test
  • "Just checking" a network you happen to be on
  • Anything where you cannot name who authorized it

Install

Python 3.10+. nmap is the only hard requirement — everything else enables more tools and is optional.

curl -fsSL https://raw.githubusercontent.com/0x45dd/pwner/main/install.sh | bash

That builds an isolated virtualenv under ~/.local/share/pwner, leaves your system Python alone, and puts pwner on your PATH so it works from any directory. Add --with-tools to install nmap and the optional scanners too, and --uninstall to take it all back out. On macOS there is also a tap:

brew install 0x45dd/tap/pwner

That brings nmap with it. From source instead:

git clone https://github.com/0x45dd/pwner.git
cd pwner
python3 -m venv .venv && source .venv/bin/activate
pip install -e .

Then the external tools, and a check:

./scripts/install-deps.sh   # brew/apt/kali/pacman/dnf
pwner doctor               # what's missing

install-deps.sh installs only what is missing and skips anything your distro does not package instead of failing. pwner doctor reports every external tool, what each one enables, and the install command for the system you are on; it exits non-zero only when nmap is missing, so it works as a setup check in a script.

On Kali there is almost nothing to install. A default Kali image already ships 8 of the 11 external tools pwner drives — nmap, hydra, netexec, impacket, certipy, searchsploit, hashcat and snmpwalk. sudo apt install nuclei bloodhound.py adds two more, and only kerbrute is unpackaged. pwner detects Kali as its own ecosystem rather than as Debian, so pwner doctor hands you apt commands instead of the source builds a plain Debian box needs. If you are choosing where to run this, run it there.

uv, or just the command
uv venv && uv pip install -e .
pipx install -e .
Docker

The whole toolchain in one image, nothing installed on your host. The base is Kali, so every tool but kerbrute comes from apt:

docker build -t pwner .
docker run --rm -it --net=host \
  --cap-add=NET_RAW --cap-add=NET_ADMIN \
  -v pwner-config:/root/.config/pwner \
  -v pwner-sessions:/root/.local/share/pwner \
  pwner

--net=host and the two capabilities are what let nmap do SYN scans and ICMP/ARP discovery; without them it quietly falls back to slower connect scans and finds fewer hosts. The volumes persist your key and your saved transcripts.

The image is ~2.7 GB and carries 10 of the 11 external tools plus nuclei's 14k templates, baked in so the first scan does not stall on a download. kerbrute is the one omission: it has no Kali package and no arm64 release. Built and run on arm64; the package set resolves on amd64 too.

First run

pwner

The first run asks for a provider and an API key once and saves them to ~/.config/pwner/config.json (owner-only). Every run after that drops you straight into a session. Change it later with pwner --pick for a provider/model menu, pwner setup for the full prompts, or /model and /provider from inside a running session.

pwner chat 10.0.0.5          # session, target set
pwner 10.0.0.0/24 --no-ai    # sweep, no model
pwner doctor                 # tool status
pwner --pick                 # choose a provider/model, then open a session

In a session you talk to the agent and steer it while it works; /help lists the commands, /findings and /topology show what it has built so far, and /dump writes the transcript to ~/.local/share/pwner/sessions/ (owner-only — it contains every credential the engagement turned up, in cleartext).

Your model

Point it at a provider and give it a key. Presets cover OpenAI, Anthropic, Google and DeepSeek; anything else that speaks the OpenAI API works via --base-url.

export PWNER_API_KEY=...
pwner chat 10.0.0.5 --provider anthropic

The model has to support tool calling. pwner chat --help lists the presets and every override; each preset's default model is in pwner/llm.py. Model names move too fast to be worth pinning in a README.

On your own hardware

ollama and lemonade are presets too, and a local endpoint needs no API key — if one is given it is still sent, since Lemonade does separate inference from admin keys.

pwner chat 10.0.0.5 --provider ollama
pwner chat 10.0.0.5 --provider lemonade

Local servers are named after whatever that machine has pulled, so there is no sensible default model. When none is set, pwner asks the server's /models endpoint and uses what it finds; pwner setup lists them and lets you pick one to save. Any other OpenAI-compatible server — llama.cpp, LM Studio, vLLM — works the same way with --base-url, and is treated as keyless whenever the URL points at loopback or a private network.

pwner chat 10.0.0.5 --base-url http://192.168.1.50:13305/api/v1 --model Qwen3-Coder-30B

Running without a model

--no-ai runs the deterministic suite: host discovery, port scan, service detection, OS fingerprint and known-issue detection, then the same findings report. No API key, no network calls beyond the scan itself, and reproducible — which makes it the mode to use in CI, on an air-gapped engagement, or when the data cannot leave the building.

The active tools are the agent's; they need both a model and your consent.

PCI DSS segmentation testing

Two of the things PCI DSS v4.0.1 asks for are pure network-layer work, and pwner pci does them with no model and no agent:

pwner pci 10.0.0.0/24                       # is the CDE isolated from here?
pwner pci 10.0.0.0/24 --json > evidence.json

Run it from a segment that is out of scope for the CDE. It tries to reach the CDE and reports whether the boundary held — that is 11.4.5, or 11.4.6 on the six-month cycle if you are a service provider. Because it is deterministic, re-running it after a fix is the retest 11.4.4 asks for, and the exit code makes that a gate: 0 isolated, 2 not isolated, 3 inconclusive.

It is not asking what a port scan asks. A port scan wants to know what is listening; this wants to know whether a packet can get there at all, so a closed port that answers with a RST is a failure of isolation rather than a pass — something on the path replied. Whether that RST came from the host or from a firewall rejecting on its behalf cannot be told from a TCP connect, so that case is reported as inconclusive rather than guessed in either direction. Settle those from the segmentation control's own logs.

Documentation

Full command and config reference, the agent loop, the module catalogue and the rules live at pwner.si:

  • Docs — install, first session, every command and flag
  • Agent — how the loop works and what it can and cannot do
  • Modules — all 52 tools and what each one drives
  • Rules — scope, consent, responsible use
  • Contributing — development setup, the tools package, adding a tool
  • Security — reporting a vulnerability in pwner itself

Contact

For anything that does not belong in a public issue — questions, private feedback, or getting in touch directly — email berk@planckproof.ai.

Bugs and feature requests work better as issues, and security vulnerabilities should go through SECURITY.md so they stay private until there is a fix.

License

GNU AGPL-3.0-or-later. Copyright (C) 2026 Berk D.

Strong copyleft, with a network clause: if you modify pwner and let other people use it over a network, you owe them the modified source too.

Metadata

Release files for pwner 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pwner 0.3.2
File Size Uploaded Provenance Actions
pwner-0.3.2.tar.gz 165.6 kB With provenance Details for pwner-0.3.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pwner 0.3.2
File Interpreter ABI Platform Actions
pwner-0.3.2-py3-none-any.whl Python 3 none any Details for pwner-0.3.2-py3-none-any.whl

Total release size: 296.7 kB

Release files / pwner-0.3.2.tar.gz

Download URL pwner-0.3.2.tar.gz
Size 165.6 kB
Tags Source
SHA-256 checksum
How to use checksums
a3863d212635bf025ced457ea977b2213f72b23d0f3b1a614cdbf6f66a4b5e99
BLAKE2b-256 checksum
How to use checksums
5fc4547be45f2eff8f27a7cd9ac0333018ab1b4f397bc750b0d94ca2b36c7139
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Release files / pwner-0.3.2-py3-none-any.whl

Download URL pwner-0.3.2-py3-none-any.whl
Size 131.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2996d2a8958dcfd4c3424506fc0b077b16868614f0c91a640d6daf24552d3c2b
BLAKE2b-256 checksum
How to use checksums
fc39b85015bde2ec5d4e7b0825715e46dadabff00cb122a2ea469e6b9d951c5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Security for pwner 0.3.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pwner-0.3.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.3.2 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page