Observe wide, act narrow.
Early modules only look. Later ones confirm a single hop. Nothing in the core changes a target.
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.
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).
Two more candidate paths exist in the sample run and are drawn only on wider screens.
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.
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
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 defaultone 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.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."ruleId": "HYG-0007", "level": "error", "message": { "text": "credential outlives policy" }, "locations": [{ "logicalLocations": [{ "name": "svc-backup" }] }], "properties": { "state": "validated", "scope": "LAB-0042" }
{
"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.
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 entryslot: probeOpen.no registry yetslot: verifyOpen. Reviewed by hand before listing.no registry yetRegistry slots are an example layout. There is no live registry.
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.
| Module | Reads | Never | Level |
|---|---|---|---|
| discover | Host liveness inside allowed ranges | Contacts an address outside the allowlist or above your rate | probe |
| enumerate | Service banners and directory lookups | Sends malformed input or guesses credentials | probe |
| paths | The stored graph | Opens a connection, or ranks a denied asset as reachable | passive |
| hygiene | Account age, group membership, directory settings | Reads hashes, tries a login, or stores a credential | probe |
| validate | One hop at a time, with read-only checks | Writes, creates or modifies anything on a target | verify |
| report | Run evidence and the audit log | Marks an unvalidated finding as confirmed, or sends data off the machine | passive |
| scope | Allowlist, deny list, window, rules of engagement | Has a bypass flag. No scope file, no run | passive |
| agent | The graph and the module outputs | Acts outside the modules above, or skips the approval gate you set | verify |
| plugin host | Plugin manifests | Grants a capability the manifest did not declare | planned |