MODULESsmall parts, each with a declared safety level

Six modules that read, one gate that decides.

pwner is a set of separate modules you can run by hand or let the agent chain. Each one states what it reads, what it will never do, and how much it can touch. This page lists the six pipeline modules, then the scope engine, the agent and the planned plugin host.

Authorized testing only. Every module checks the scope file before it acts, and every action lands in the audit log. No scope file, no run. Read the rules

  1. discoverprobe
  2. enumerateprobe
  3. pathspassive
  4. hygieneprobe
  5. validateverify
  6. reportpassive
scope checks every step first
Run order, left column. Right column is the declared level.
PIPELINEone run, left to right

Observe wide, act narrow.

Early modules only look. Later ones confirm a single hop. Nothing in the core changes a target.

DISCOVER, ENUMERATElevel: probe

Know what is there before you ask what is wrong.

Host sweeps and service fingerprinting stay inside your allowlist and under a rate you set. A passive-only mode reads what the network already says and sends nothing.

  • ReadsHost liveness, open service banners, directory lookups for names and groups.
  • RateSet with --rate. The scope file can cap it lower, never higher.
  • NeverSends malformed traffic, guesses logins, or touches a host the deny list names.
~/engagements/acme-demosample, fictional lab
sample data, fictional lab. Output is stylized.
PATHSlevel: passive, no network traffic

Paths, not piles of findings.

The graph module only reads what earlier modules stored. Ask it for the shortest route to a named asset and it answers from the graph, with a hop count and a scope verdict for each edge.

  • validated hop
  • candidate path, not yet checked
  • out of scope, skipped
  • known relationship

Sample graph, fictional lab hosts. Flags: --from, --to, --max-hops (example).

Attack-path graph of a fictional lab network Hosts and accounts in a fictional lab. The highlighted route runs from ws-0031 through svc-backup and files01 to dc01, three validated hops. A fourth hop from dc01 to dc02 is skipped because dc02 is on the deny list. Two more candidate paths are drawn dotted. ws-0031svc-backupci-runnerdb02intranetdc02 files01dc01 10.0.0.31account10.0.0.4410.0.0.13 denied 10.0.0.2010.0.0.12 1234 out of scope #2#3 Attack path, vertical view The route runs from ws-0031 through svc-backup and files01 to dc01 over three validated hops. A fourth hop to dc02 is skipped because dc02 is denied by the scope file. 1234 ws-0031svc-backupfiles01dc01dc02 10.0.0.31account10.0.0.2010.0.0.1210.0.0.13 denied out of scope, skipped

Two more candidate paths exist in the sample run and are drawn only on wider screens.

HYGIENElevel: probe, read-only

Identity checks that name the weakness, not a recipe.

Most paths run through accounts, not exploits. The hygiene module reads directory metadata and reports where setups are looser than they need to be. Coverage is basic for now.

  • Stale accountsService accounts with passwords older than your policy, or no recent sign-in.
  • Broad groupsNested groups that give more reach than the role needs.
  • Shared adminsOne local admin password reused across many workstations, detected by metadata, not by reading secrets.
  • Open bindsDirectory settings that allow anonymous reads from user networks.

It never reads password hashes, never tries a login, and never stores a credential. A finding holds names, ages and counts.

finding-0007.yamlexample output
id: HYG-0007
check: stale-service-account
subject: [email protected]
observed:
  password_age_days: 1460
  last_sign_in_days: 212
  member_of: 3 # groups, names in graph
weakness: credential outlives its policy
reads_secret: false
state: observed # not validated yet
fix: rotate, scope to one host, set expiry
VALIDATElevel: verify, the highest in core

Confirm one hop. Change nothing.

Validation proves that a hop exists using checks that read and never write. If a hop leaves scope, it is skipped and logged, and the run goes on.

passiveReads stored data only. No traffic leaves the machine.
probeRate-limited reads against allowed hosts. Nothing is created or altered.
verifyAuthenticated reads that confirm a single hop. Needs the approval gate at the autonomy level you chose.
changeNot in core. A plugin must declare it, the scope file must opt in, and a person must approve each use.off by default
one hop per checkA path is confirmed edge by edge, so a refusal at hop 4 does not undo hops 1 to 3.
deny list winsAn asset on the deny list is never contacted, even when it sits on the shortest path.
approval gateWith --autonomy approve-each-step, each verify step waits for a yes from the operator.
evidence, not exploitationOutput is what was read and when. No payloads are produced or stored.
dry run firstAdd --dry-run to see every call the module would make, with the scope verdict.
REPORTlevel: passive

One run, three outputs.

Only validated findings are marked confirmed. The same run writes a file for CI, one for your tooling and one for people.

.sarifFor code scanning and any SARIF viewer.
.jsonVersioned schema for your own tooling.
.mdFor a pull request or the client write-up.
acme-demo.sarifexcerpt, example
"ruleId": "HYG-0007",
"level": "error",
"message": {
  "text": "credential outlives policy"
},
"locations": [{
  "logicalLocations": [{
    "name": "svc-backup"
  }]
}],
"properties": {
  "state": "validated",
  "scope": "LAB-0042"
}
acme-demo.jsonexcerpt, example
{
  "schema": "pwner.report/vX",
  "hosts": 14,
  "paths": [{
    "id": "path-1",
    "hops_total": 4,
    "hops_confirmed": 3,
    "skipped": [{
      "to": "dc02",
      "why": "deny list"
    }]
  }],
  "audit_actions": 212
}

sample data Fictional lab, placeholder schema name and version.

PLUGINSplanned, not shipping yet

Add a module. Declare what it touches.

The plugin SDK is on the roadmap. A plugin will be a Go package or a sandboxed WASM component with a manifest. The scope engine reads the manifest and refuses anything the plugin did not declare.

The manifest sketch lives on the architecture page, next to the scope engine that enforces it. See the roadmap for status.

slot: passive / exampleA passive exporter that turns graph hosts into an inventory file. Declares no writes and no network.placeholder entry
slot: probeOpen.no registry yet
slot: verifyOpen. Reviewed by hand before listing.no registry yet

Registry slots are an example layout. There is no live registry.

REFERENCEevery module, one row

What each module reads and never does.

Status and levels are what the project aims to ship. Treat names as placeholders until a release exists.

Modules, what they read, what they never do, and their safety level
ModuleReadsNeverLevel
discoverHost liveness inside allowed rangesContacts an address outside the allowlist or above your rateprobe
enumerateService banners and directory lookupsSends malformed input or guesses credentialsprobe
pathsThe stored graphOpens a connection, or ranks a denied asset as reachablepassive
hygieneAccount age, group membership, directory settingsReads hashes, tries a login, or stores a credentialprobe
validateOne hop at a time, with read-only checksWrites, creates or modifies anything on a targetverify
reportRun evidence and the audit logMarks an unvalidated finding as confirmed, or sends data off the machinepassive
scopeAllowlist, deny list, window, rules of engagementHas a bypass flag. No scope file, no runpassive
agentThe graph and the module outputsActs outside the modules above, or skips the approval gate you setverify
plugin hostPlugin manifestsGrants a capability the manifest did not declareplanned