ARCHITECTUREone process, files on disk

Small on purpose.

One static binary, one engagement folder, and a policy layer that sits outside the model. This page follows a single request from the planner to the target and back, then shows where each piece lives.

plannerscope enginemodulesgraph.db, pwner-audit.jsonl proposes onlyallow, deny or askact as declaredappend-only
One request, top to bottom.
DATA FLOWone request, end to end

Nothing reaches a target without passing the scope engine.

The planner only proposes. The scope engine, a separate component in core, allows or refuses. The autonomy level decides who confirms, and the audit log records every step, refused ones included.

pwner data flow The planner proposes an action. The scope engine, which reads scope.yaml, checks it and either refuses it or passes it to the approval gate. The module runner then acts on in-scope targets and writes observations to the graph store. The graph store feeds the next plan and the report. The audit log records every step. pwner-audit.jsonl, append-only: every step below is written here plannermodel or fallback scope engineoutside the model approval modulesbuilt in or plugin targets10.0.0.0/24 (lab) graph storegraph.db, local reportsarif, md, json scope.yamlyou write it refusedlogged, not sent next plan reads the graph pwner data flow, stacked Top to bottom: planner, scope engine (reads scope.yaml, refuses out-of-scope requests), approval gate, modules acting on targets, graph store feeding the report and the next plan. The audit log records every step. plannermodel or fallback scope engineoutside the model scope.yaml refused approval modulesbuilt in or plugin targets10.0.0.0/24 graph storegraph.db, local report pwner-audit.jsonl, append-onlyevery step above is written here

Sample flow, fictional lab range. Select a step or let it cycle.

POLICY LAYERcore/scope

The model asks. The scope engine answers.

Scope is a plain file you write before the first run. A small piece of core code evaluates it for every proposed action. Modules, plugins and the planner never see a way to edit it, so a confused or manipulated model cannot widen its own reach.

Authorized testing only. Run it against systems you own or have written permission to test. Read the rules

proposalrule that decidedverdict
enumerate 10.0.0.21allow[0] 10.0.0.0/24allow
resolve dc01.corp.acme-demo.exampleallow[1] corp.acme-demo.exampleallow
enumerate 10.0.0.13deny[1] 10.0.0.13deny
reach 203.0.113.50no allow rule matchesdeny
change state on 10.0.0.30autonomy: approve-each-stepask

Illustrative decisions on a fictional lab. Default is deny: no matching allow rule means refused. Every row above, refused ones too, becomes an audit entry.

PACKAGINGone binary, one folder

You can read the whole data path in an afternoon.

A static build with no runtime to install. Copy it to a jump box, point it at an engagement folder, and it works offline. Everything it writes lands in that folder.

pwner one static binary ├─ core/ scope engine, graph, scheduler ├─ agent/ planner interface, approvals, budget ├─ modules/ discover, enumerate, paths, │ hygiene, validate, report ├─ plugins/ sandboxed, planned └─ playbooks/ yaml, yours engagement/ ├─ scope.yaml checked first ├─ graph.db embedded graph store ├─ pwner-audit.jsonl append-only └─ report/ sarif, md, json
  • Single binaryStatic build, no interpreter, no container needed. Updating means replacing one file.
  • Core is the boundaryScope, graph and scheduler live in core. The agent package imports core. Core never imports the agent.
  • PlaybooksRepeatable runs as YAML. Diff them, review them, attach them to the ticket.
  • Offline by defaultWith the fallback planner there is no network use beyond your in-scope targets.
EXTENSION POINTSplugins and planners

Two places to plug in.

Both are narrow on purpose. A plugin adds an action. A planner chooses actions. Neither can change the scope.

Plugins declare what they touch.

A manifest lists hosts, ports, files and mode. The scope engine enforces it at call time, so a plugin that asks for more than it declared is refused. The plugin SDK is not out yet; this is the plan.

plugin.yamlexample manifest, planned
name: smb-share-audit
needs:
  hosts: in-scope only
  ports: [445]
  mode: read-only
writes: [findings]
sandbox: wasm   # planned

Planners return proposals, nothing more.

The planner reads a view of the graph and a budget, and returns a short list of proposed actions with a reason each. It gets no handle to the network and no write access to the scope.

planner interfacesketch, not the final API
planner.propose(view: GraphView, budget: Budget)
    -> list of Proposal

Proposal {
  module, target, reason
}
# the scope engine decides what happens next
fallbackDeterministic rules. No model, no network.
local modelAn endpoint on your own machine or network.
hosted modelOnly if you configure one. Receives the graph view you allow.
STATElocal-first

The folder is the product.

No account, no server, no sync. The graph, the evidence and the audit log are ordinary files you can back up, hand over to a client or delete.

  • Graph storeAn embedded database in graph.db. Nodes are hosts, services, identities and findings. Edges say what reaches or grants what. Attack paths are queries over it.
  • Audit logOne JSON line per step: target, module, time, the decision, the scope rule that decided, and who approved it if anyone did. The tool only appends. It never rewrites or truncates the file.
pwner-audit.jsonlexample entries, fictional lab
{"step":14,"action":"enumerate","target":"10.0.0.21","decision":"allow","rule":"allow[0]","by":"scope-engine"}
{"step":15,"action":"enumerate","target":"10.0.0.13","decision":"deny","rule":"deny[1]","by":"scope-engine"}
{"step":16,"action":"state-change","target":"10.0.0.30","decision":"approve","rule":"approve-each-step","by":"operator"}

No telemetry, and the egress list is short.

your targetsOnly hosts that pass the scope engine.
a model endpointOnly if you configure one. Off by default.
everything elseNothing. No analytics, no update ping, no crash reports.
DESIGN PRINCIPLESwhat we keep choosing

Rules we hold the design to.

Policy outside the modelAnything that must always hold is enforced in code the model cannot reach.
Deny by defaultIf no rule allows an action, it does not happen.
Files over servicesState is a folder you can read, copy and delete.
Log before and afterRefusals are entries too. The record is the product.
Narrow extension pointsPlugins and planners get a small surface and a declared reach.
Small enough to auditIf a piece cannot be reviewed in an afternoon, it is too big.