diff --git a/README.md b/README.md
index 83ab0f6..b108617 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@


+**[Project site](https://devcop95.github.io/pullgoscript/)** — the same information, laid out as a browsable reference: every route with the flag that unlocks it, the twelve audit controls, how to verify a release, and the security model with its two documented limits. The download table reads the newest release from the GitHub API, so it does not go stale between releases.
+
**OBLITERATUS** answers one question — *would Mimikatz work against this host?* — and answers it with evidence: registry state, token privileges, service configuration and browser store exposure, each mapped to the exact Mimikatz module and MITRE technique it corresponds to.
It ships as a single binary with two modes:
diff --git a/docs/.nojekyll b/docs/.nojekyll
new file mode 100644
index 0000000..e69de29
diff --git a/docs/audit.html b/docs/audit.html
new file mode 100644
index 0000000..6f94794
--- /dev/null
+++ b/docs/audit.html
@@ -0,0 +1,196 @@
+
+
+
+ Twelve controls, each one a condition Mimikatz depends on, each carrying a stable ID,
+ a severity, the evidence behind the verdict, a remediation and the exact module it
+ corresponds to. This is the part that runs without any flag — it is read-only and it is
+ the reason the tool exists.
+
+
+
obliteratus.exe --audit # human-readable, exit 1 when a CRITICAL/HIGH control fails
+obliteratus.exe --audit --json # machine-readable
+obliteratus.exe --audit --csv # spreadsheet
+
+
+ Checks degrade to unknown, never to a false pass.
+ If a privilege or an asset is missing, the control reports that it could not see, because an
+ audit that guesses is worse than one that admits a gap.
+
+
+
The twelve controls
+
+
+
+
Verified against a live report on a fully patched Windows 11 host (elevated context).
+ Expect exit 1 on a healthy workstation, and read the report before you call it a
+ false alarm. On a fully patched, fully elevated Windows 11 host the live report shows
+ MK-SEDEBUG failing as HIGH: the token carries
+ SeDebugPrivilege but has not enabled it, and a high-integrity admin token can
+ enable it whenever it wants — which is precisely the point the control is making. The
+ contract is about the worst severity, so that one HIGH is what makes a scripted run exit
+ non-zero.
+
+
+
Report shape
+
+
+ Findings carry id, title, severity,
+ status, evidence, remediation,
+ technique and module, alongside
+ tool, version, hostname,
+ generated_at, elevated, modules and a
+ summary count per severity. Stable IDs mean you can diff two runs across a
+ remediation window and see exactly which control moved.
+
+
+
+ Adding a check is one function plus one line in RunChecks — see
+ CONTRIBUTING.md.
+ The audit package never imports server; progress is reported through
+ an Emitter callback so the checks stay unit-testable.
+
+ The complete API surface — all 29 routes — with the flag that unlocks each one. Nothing
+ here is hidden behind a flag you cannot see: a locked route answers
+ 403 and returns the exact flag that would open it.
+
+
+
+ Every request below is a loopback request to 127.0.0.1:8080 and carries
+ X-Signal-Token: <per-run token>. The token is shown in the dashboard page
+ and printed (short form) at startup; it is regenerated on every launch.
+ See Security model for what that token does and does not protect.
+
+
+
Page and channel routes
+
+
+
+
No token: these cannot carry one on first load. The loopback Host check is what makes them safe to serve.
+
Route
Guard
What it serves
+
+
GET /
loopback Host
The dashboard (token injected into the page, same-origin only)
+
GET /healthz
loopback Host
Liveness, ok
+
GET /ws
loopback Host + loopback Origin + ?t=
WebSocket console channel; browsers cannot attach headers to a WS handshake, so the token travels in the query string
+
+
+
+
+
Always available (token only)
+
+
+
+
Compiled in, reachable with the per-run token, no flag required.
+
Route
What it does
Technique
+
+
GET /v1/sys/st
Execution context: reports USER or ADMIN for the process token
—
+
GET /v1/sys/b2
Live process table (JSON) for PID targeting
—
+
GET /v1/sys/hex
First 256 bytes of the payload file, hex-encoded — payload integrity inspection
—
+
POST /v1/sys/cmd
Command console: executes a shell command as the token holder
—
+
POST /v1/sys/opt
Telemetry/privacy hardening: writes HKLM policy (AllowTelemetry, WER) and stops + disables DiagTrack. No flag, and it takes no body.
—
+
POST /v1/audit/report
The audit checklist as JSON
—
+
POST /v1/audit/csv
The audit checklist as CSV
—
+
+
+
+
+
Credential collection — --enable-collect
+
+
+
+
Auto-elevates via UAC at startup: the SAM hives, the LSASS minidump and the browser App-Bound key unwrap cannot work with a filtered token.
+
Route
What it does
Technique
+
+
GET /v1/sys/a7
Browser credential inventory
T1555.003
+
GET /v1/sys/cookies
Browser cookie inventory
T1555.003
+
GET /v1/sys/nexus
Identity Nexus: the identity picture assembled from local browser stores
T1555.003
+
POST /v1/op/a7
Streamed credential collection
T1555.003
+
POST /v1/op/cookies
Streamed cookie collection
T1555.003
+
POST /v1/sys/sam
Exports the local SAM and SYSTEM hives with RegSaveKeyEx and walks the [MS-SAMR] hash chain
T1003.002
+
POST /v1/op/lsass
LSASS minidump — the sekurlsa::logonpasswords path
T1003.001
+
POST /v1/op/lsass-wipe
Shreds the collected LSASS material
T1003.001
+
POST /v1/op/roast-asrep
AS-REP roasting against accounts without pre-auth
T1558.004
+
POST /v1/op/roast-tgs
Kerberoasting: requests service tickets for SPNs
T1558.003
+
POST /v1/op/spn-enum
SPN enumeration, the recon step before roasting
T1558.003
+
+
+
+
+
Persistence — --enable-persist
+
+
+
+
Four backends behind one manager, each mapped to its technique.
+
Route
What it does
Technique
+
+
GET /v1/sys/persist
Enumerates all four backends — Run keys, Startup folder, scheduled tasks, WMI subscriptions
—
+
POST /v1/op/persist-add
Creates one foothold: registry Run key, Startup folder file, schtasks entry, or a CommandLineEventConsumer
T1547.001 · T1547.009 · T1053.005 · T1546.003
+
POST /v1/op/persist-del
Removes one; a missing entry is not an error
same four
+
+
+
+
+
Self-evasion — --enable-evasion
+
+
+
+
Affects this process only. Both patches are logged with the resolved address, never applied silently.
+
Route
What it does
Technique
+
+
POST /v1/op/etw
Neuters ntdll!EtwEventWrite in the tool's own process (W^X-safe transition)
T1562.006
+
POST /v1/op/amsi
Patches amsi!AmsiScanBuffer in the tool's own process
T1562.001
+
+
+
+
+
+ Repatching is refused with 422 already patched rather than applied twice — a
+ blind second patch would corrupt the prologue it already rewrote.
+
+
+
Lateral movement — --enable-lateral
+
+
+
+
Credentials come from the operator in the request body; nothing is stored.
+
Route
What it does
Technique
+
+
POST /v1/op/lateral-winrm
Runs a command on a remote host over WS-Management (SOAP, Basic auth)
T1021.006
+
POST /v1/op/lateral-scm
Remote execution through the Service Control Manager: create, start, delete
T1021.002 · T1569.002
+
+
+
+
+
Process injection — --enable-inject
+
+
+
+
Route
What it does
Technique
+
+
POST /v1/op/d4
Halo's Gate indirect-syscall injection into an operator-selected local PID. amd64 only. RW → RX transition: no RWX page ever exists.
T1055
+
+
+
+
+
+ The payload is unpacked in memory right before the injection. Pack it with
+ oblpack.exe -in payloads\research_signal.bin -key <passphrase> and start the
+ console with the same --payload-key. The envelope is
+ magic || nonce || AES-256-GCM, so the on-disk bytes match no public signature.
+ The research note on SSN recovery and the W^X design is in
+ docs/HALOS_GATE.md.
+
+
+
Examples
+
+
Request shapes as the handlers enforce them; a wrong shape answers 400 with the expected body.
+
+
# execution context and process table
+curl -s -H "X-Signal-Token: $TOK" http://127.0.0.1:8080/v1/sys/st
+curl -s -H "X-Signal-Token: $TOK" http://127.0.0.1:8080/v1/sys/b2
+
+# command console (runs as the token holder)
+curl -s -X POST -H "X-Signal-Token: $TOK" \
+ -d '{"c":"whoami"}' http://127.0.0.1:8080/v1/sys/cmd
+
+# persistence: list, then add a Run key
+curl -s -H "X-Signal-Token: $TOK" http://127.0.0.1:8080/v1/sys/persist
+curl -s -X POST -H "X-Signal-Token: $TOK" \
+ -d '{"type":"run","hive":"HKCU","name":"x","cmd":"C:\\path\\to\\binary.exe"}' \
+ http://127.0.0.1:8080/v1/op/persist-add
+
+# lateral movement over WinRM
+curl -s -X POST -H "X-Signal-Token: $TOK" \
+ -d '{"host":"10.0.0.5","user":"CORP\\svc","password":"…","command":"whoami"}' \
+ http://127.0.0.1:8080/v1/op/lateral-winrm
+
+# injection into a selected PID (amd64)
+curl -s -X POST -H "X-Signal-Token: $TOK" \
+ -d '{"pid":1234}' http://127.0.0.1:8080/v1/op/d4
+
+
+ Two things worth knowing before you unlock anything.
+ POST /v1/sys/opt modifies the machine — HKLM policy and the
+ DiagTrack service — and unlike every other state-changing capability it requires
+ no flag, only the token. And the token itself is handed to any process that can reach
+ loopback and fetch the dashboard page, so the console is a single-user tool by design:
+ the boundary is the machine, not the user account. Both are documented under
+ Security model.
+
The Mimikatz playbook, run as a defensive checklist.
+
+
+ Windows 10/11 · amd64 & arm64
+ Go 1.26.9+
+ MIT
+ v2.0.2
+ cosign keyless signature
+ not Authenticode-signed
+
+
+
+
+
+ OBLITERATUS answers one question — would Mimikatz work against this host? —
+ and answers it with evidence: registry state, token privileges, service configuration
+ and browser store exposure, each mapped to the exact Mimikatz module and MITRE ATT&CK
+ technique it corresponds to.
+
+
+
+ It ships as a single binary with two modes. The headless audit is safe to run in CI and
+ exits non-zero when a CRITICAL or HIGH control is failing. The dashboard is a local
+ console on 127.0.0.1 with a per-run 256-bit token.
+
Operators who want the interactive console on loopback
+
+
+
+
+
+ Every offensive capability is compiled in but locked.
+ A locked route answers 403 and names the flag that would unlock it, until the
+ operator passes that flag explicitly. Unlocking prints a
+ [!] ... UNLOCKED banner at startup — the effect is never silent.
+ The full route-by-route gate is in Capabilities.
+
+
+
Download
+
+
+ Pinned list for v2.0.2. With JavaScript available this is replaced by the live
+ release from the GitHub API.
+
+ Provenance. The checksum file is signed keyless with cosign, bound to the
+ workflow run and logged in the Sigstore transparency log. Pin the identity — a bare
+ Verified OK only says someone signed it:
+
+ Windows will still complain, and that is not a build failure.
+ The .exe files are not Authenticode-signed:
+ Get-AuthenticodeSignature reports NotSigned, SmartScreen shows
+ "Windows protected your PC", and Defender may quarantine on sight of the injection or
+ credential-collection strings. A trusted Authenticode signature needs an EV certificate or
+ an Azure Trusted Signing account — a paid, identity-verified credential this repository
+ cannot hold. Until one exists, the cosign signature is the honest guarantee:
+ this binary came from this repository's release workflow, even though Windows
+ will not validate it natively. Details and the operator/local signing path are in
+ Verify.
+
+ Prefer this to the archives if you want to read the code you are about to run. The
+ toolchain floor is Go 1.26.9 — the standard library ships inside the binary, so an old
+ toolchain is a vulnerable dependency.
+
+ OBLITERATUS is for authorized security assessment, research and education on systems you
+ own or are contracted to test. The credential-collection, persistence, evasion,
+ lateral-movement and injection modules are deliberately locked; unlocking them is an
+ explicit acknowledgement that your engagement permits their use. Unauthorized access to
+ computer systems is illegal.
+
+ 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.
+
Sev
v1 problem
v2 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 recorded as open design questions in
+ issue #6, with the options and
+ their trade-offs, rather than left to be discovered by whoever reads the code next.
+
+
+
Intentional capabilities
+
+
+ These are features, not bugs — they are the reason the tool exists:
+
+
+
+
POST /v1/sys/cmd executes shell commands as the token holder.
+
--enable-collect decrypts browser credential stores and the local SAM/SYSTEM
+ hives on the local host.
+
--enable-inject performs W^X process injection into an
+ operator-selected local PID.
+
--enable-persist creates and removes local persistence footholds.
+
--enable-lateral executes commands on operator-supplied remote
+ hosts over WinRM or SCM.
+
--enable-evasion neuters ETW/AMSI in this process only.
+
+
+
+ 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.
+
+ OBLITERATUS ships offensive modules, so the artifacts will trip SmartScreen and Defender no
+ matter what. This page is about the guarantees that can be made cryptographically,
+ and the one that cannot be made yet.
+
+
+
What the pipeline produces
+
+
+ .github/workflows/release.yml runs on every v* tag and emits,
+ per release:
+
+
+
+
+
Artifact
What it proves
+
+
obliteratus_<ver>_windows_<arch>.zip
The binaries plus README, SECURITY.md and LICENSE
+
checksums.txt
SHA-256 of every archive
+
checksums.txt.sig
cosign keyless signature over checksums.txt
+
checksums.txt.pem
The short-lived certificate binding that signature to the workflow run
+
*.sbom.json
SPDX SBOM (syft) — full dependency inventory per archive
+
+
+
+
+
+ There is no long-lived signing key in this repository. The keyless signature
+ is bound to the GitHub Actions OIDC identity of the workflow run and recorded in the Sigstore
+ Rekor transparency log, so it cannot be forged without compromising GitHub's
+ OIDC issuer — and any use of that identity is publicly logged.
+
+ Transparency log entry — proves it was logged, not merely signed.
+
rekor-cli get --artifact checksums.txt
+
+
+
+
+ The identity flag is the crux. A bare Verified OK only says
+ someone signed the file. Pinning the identity to DevCop95/pullgoscript is
+ what stops a signature from any other repository being accepted. For v2.0.2 the certificate
+ subject is exactly:
+
+ Use --certificate-identity (singular, exact match) to pin one tag instead of the
+ repository. Note that the subject embeds the repository name at the time of the release,
+ so a later rename invalidates an exact pin — which is why the release workflow builds its
+ self-check from ${GITHUB_REPOSITORY} and the command above uses the repo-prefix
+ regexp.
+
+
+
+ cosign v3 prints Flag --certificate has been deprecated… for the two flags above.
+ That is expected: the release ships the detached .sig + .pem pair and
+ the warning refers to the newer single-file --bundle form. The verification result
+ is unaffected.
+
+
+
Reviewing the dependency inventory
+
+
syft convert obliteratus_2.0.2_windows_amd64.sbom.json -o table
+# or grep for the modules you actually care about
+
+
Authenticode: not applied
+
+
The .exe files are not Authenticode-signed. Concretely:
SmartScreen shows "Windows protected your PC"; Defender may quarantine on sight of
+ the injection / credential-collection strings
+
+
+
+ This is expected for an unsigned offensive tool and is not a build failure.
+ A signature Windows actually trusts needs an EV certificate or an
+ Azure Trusted Signing account — a paid, identity-verified credential that must
+ live in a secret. No placeholder certificate is checked into CI, because a placeholder would
+ prove nothing while looking like it did.
+
+
+
+ Until that credential exists, the cosign signature is the honest guarantee:
+ this binary came from this repository's release workflow, even though Windows will not
+ validate it natively. The gap is tracked publicly in
+ issue #5.
+
+
+
If you hold a certificate
+
+
You can sign a locally built binary today with a cert in CurrentUser\My:
+ Timestamping with /tr is not optional: without it the signature dies the moment the
+ certificate expires.
+
+
+
+
+
Supply-chain status
+
Step
State
+
+
cosign keyless signature over checksums.txt
live · v2.0.2 verified
+
SPDX SBOM per archive
live
+
SHA-256 checksums
live
+
Authenticode on the .exe files
blocked on a certificate
+
+
+
+
+
What provenance is not
+
+
+ The cosign guarantee is about provenance — which repository and workflow built
+ this — not about safety. OBLITERATUS is dual-use by design. Provenance, a
+ readable SBOM and a pinned toolchain are the whole point; reading the code you are about to run
+ is still the stronger move, and building from source is documented on the
+ front page.
+