OBLITERATUS

Security model

A tool that demonstrates credential theft has to survive the question "what if it is pointed at me?". v1 did not; v2 was rebuilt around the answer, and the test suite pins each control closed.

What v1 got wrong

Severity as assessed against the v1 code.
Sevv1 problemv2 control
CRIT Listened on :8080 — the console was LAN-reachable Binds 127.0.0.1 only; ListenAndServe refuses a non-loopback address
CRIT Token derived from the PID — visible to any process — and echoed to unauthenticated GET / 256-bit crypto/rand token, regenerated per run, compared in constant time
CRIT CheckOrigin: return true — any web page could open the WebSocket and drive the console Origin must be loopback and the ?t= token must match
CRIT No Host validation — DNS rebinding could read the token out of the page Every route rejects a non-loopback Host
HIGH Mixed GET mutations, no method enforcement ServeMux method patterns: automatic 405 on the wrong verb
HIGH Token in HTML readable cross-origin (no CSP, no headers) Same-origin only, plus CSP, nosniff, DENY framing, no-store
HIGH DOM XSS: database-derived URLs rendered through innerHTML All server data escaped through window.esc() before it reaches the DOM
MED Auto-UAC on every start Elevation only when asked for: --elevate, or a module that cannot work unelevated
MED Credentialed collectors reachable by default Locked behind --enable-*, failing closed with an actionable 403
MED Privileged modules failed opaquely — SAM errors looked like "no SAM material", LSASS failures hung the UI The real cause is streamed to the dashboard (sam_error / lsass_error), UAC is requested up front, and a relaunch that yields no elevated token fails loudly instead of looping

Where the boundary sits

Anyone who can read the dashboard page — loopback, same origin — is the operator. That is the design: the console answers to the token, and the token is a lock on the door, not on the room.

Against a hostile web page, the boundary is airtight, and each layer is independent:

It can reach the port

But a cross-origin fetch() cannot read the response — same-origin policy.

It cannot set the header

The custom X-Signal-Token header forces a CORS preflight, which is never approved, so the request is never sent. This is what protects state-changing routes from CSRF.

It cannot guess the token

256 bits from crypto/rand, regenerated per run, compared in constant time.

Rebinding gets nothing

A rebinding page reaches the port with its own hostname in Host, which is refused before any handler runs.

Two limits, documented rather than hidden

1. The token defends against remote pages, not against local processes. GET / serves the dashboard with the token inside it, and that route is guarded only by the loopback Host check. Any process that can reach loopback — including one running as a different local user, since loopback is machine-scoped — can read the token and then use POST /v1/sys/cmd to execute commands as the user running the console. The practical consequence is a deployment rule: run the console on a single-user workstation. On a shared or RDP host it is a cross-user escalation path.
2. POST /v1/sys/opt changes the machine with no flag. It writes HKLM policy and stops + disables the DiagTrack service, and unlike every other state-changing capability it requires only the token — there is no --enable-* gate, because the action is privacy hardening rather than credential access. It is listed on the Capabilities page so nobody discovers it by accident.

Both are written into the repository's SECURITY.md under "Documented limits" rather than left to be discovered by whoever reads the code next. The discussion, the trade-offs and the rejected options live in issue #6, which is closed.

Intentional capabilities

These are features, not bugs — they are the reason the tool exists:

They are reachable only over loopback, only with the per-run token, and the credential, persistence, evasion, lateral and injection modules additionally require their flag at startup. Hardening regressions in that chain are in scope for vulnerability reports.

Out of scope

Physical access, kernel-level attackers, and the operator console itself once the token holder is trusted. Reports about the intentional capabilities above are answered with a pointer to this page unless they show the gate failing — a route reachable without its flag, a non-loopback bind, a missing Host or Origin check, a token comparison that leaks, or a state-changing route that a hostile page can reach.

Reporting a vulnerability

Report privately, never in a public issue with exploit details: GitHub → Security → Report a vulnerability on DevCop95/pullgoscript, or open a minimal issue titled [SECURITY] asking for a private channel. Include the affected version or commit, impact, reproduction steps and any suggested fix. Expect an acknowledgement within 72 hours and a status update within 10 business days; fixes are credited in the release notes unless you prefer otherwise. The full policy is in SECURITY.md.