Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Bug report
description: A reproducible defect in the audit, the console or a build artifact.
title: "[bug] "
labels: ["bug", "needs-triage"]
body:
- type: markdown
attributes:
value: |
> **Not for vulnerabilities.** If you found a flaw in OBLITERATUS
> itself (auth, token handling, injection, audit correctness), use
> [private vulnerability reporting](https://github.com/DevCop95/pullgoscript/security/advisories/new)
> instead — see [SECURITY.md](https://github.com/DevCop95/pullgoscript/blob/main/SECURITY.md).
- type: checkboxes
id: preflight
attributes:
label: Preflight
options:
- label: I am reporting a bug, not a security vulnerability, and not "my AV flagged this" (that is expected — see docs/SIGNING.md).
required: true
- label: I agree to follow this project's Code of Conduct.
required: true
- type: input
id: version
attributes:
label: Version / commit
description: Output of `obliteratus.exe --version`, or the commit hash if built from source.
placeholder: "2.0.2"
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened
description: The observed behaviour, and what you expected instead.
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Reproduction steps
description: Exact commands or dashboard actions. Redact tokens and host names.
placeholder: |
1. obliteratus.exe --audit --json
2. ...
validations:
required: true
- type: input
id: windows
attributes:
label: Windows build
description: e.g. Windows 11 24H2 (26100.2033), or the Server SKU and build.
validations:
required: true
- type: dropdown
id: admin
attributes:
label: Was the process elevated?
options:
- "Yes (Administrator / SYSTEM)"
- "No (standard user token)"
- "Not sure"
validations:
required: true
- type: textarea
id: flags
attributes:
label: Flags used
description: Which `--enable-*` modules were active, if any.
placeholder: "--enable-collect --enable-inject"
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs / JSON output
description: Relevant output. **Never paste an API token or a recovered credential.**
render: text
validations:
required: false
13 changes: 13 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
blank_issues_enabled: false
contact_links:
# A security tool gets drive-by "this looks like malware" issues and, worse,
# vulnerability reports filed publicly. Both have a better home.
- name: Report a vulnerability (private)
url: https://github.com/DevCop95/pullgoscript/security/advisories/new
about: Do not open a public issue for a flaw in OBLITERATUS itself. Use private vulnerability reporting.
- name: '"My AV flagged this" / Defender detections'
url: https://github.com/DevCop95/pullgoscript/blob/main/docs/SIGNING.md
about: Expected for an unsigned offensive tool — read the signing and verification doc before filing.
- name: Audit, flags and module reference
url: https://github.com/DevCop95/pullgoscript#readme
about: How the audit works, what each --enable-* flag unlocks, and the console's threat model.
57 changes: 57 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Feature request
description: A new check, module, or improvement to the audit or console.
title: "[feat] "
labels: ["enhancement", "needs-triage"]
body:
- type: checkboxes
id: preflight
attributes:
label: Preflight
options:
- label: I have read CONTRIBUTING.md, including the check design rules (locale/privilege independence, no side effects, no false passes).
required: true
- label: I agree to follow this project's Code of Conduct.
required: true
- type: textarea
id: problem
attributes:
label: The exposure this measures
description: |
What Windows misconfiguration or gap should this surface, and from whose
point of view? A check is only useful if it can answer "so what".
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed approach
description: API or registry key to read, expected severity, and the concrete remediation the Finding would recommend.
validations:
required: true
- type: input
id: technique
attributes:
label: MITRE ATT&CK technique (if any)
placeholder: T1552.001
validations:
required: false
- type: textarea
id: falsifiability
attributes:
label: How could this check be wrong?
description: |
What would make it report a false PASS, and how do we detect that
instead of guessing? Checks that cannot observe must return Unknown.
validations:
required: true
- type: dropdown
id: offensive
attributes:
label: Does it need a state-changing or offensive capability?
description: Anything that mutates the host or touches credentials must sit behind a --enable-* flag.
options:
- "No — read-only, fits the audit"
- "Yes — new or existing --enable-* module"
- "Not sure"
validations:
required: true
35 changes: 35 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!--
Thanks for the PR. Keep it to one topic; explain *why* in the description when
the diff alone does not make it obvious.
-->

## What changed

<!-- One or two sentences. Link the issue this closes, if any. -->

Closes #

## Why

<!-- The problem, not the patch. If it fixes a false PASS or a security
control, say so explicitly. -->

## How it was verified

<!-- Commands you ran and what they printed. "go test ./... passes" is the
baseline, not evidence for a behavioural change. -->

## Checklist

- [ ] `gofmt -l .` is empty and `go vet ./...` is clean.
- [ ] `go test ./...` passes locally.
- [ ] New behaviour has a test; a fix has a test that fails without it.
- [ ] No security control was weakened (token checks, Host/Origin guards,
`s.guard(...)`, fail-closed `403`s, `StatusUnknown` over false `PASS`).
- [ ] Any `//#nosec` carries a specific reason on the same line, and any rule
added to the `-exclude` list in `.github/workflows/ci.yml` has its
rationale written next to it.
- [ ] `SECURITY.md` / `docs/SIGNING.md` / `README.md` updated if a capability,
flag or artifact changed.
- [ ] No secret, token, certificate or private key is included — including in
test fixtures and example output.
107 changes: 101 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ jobs:
go test ./... -coverprofile=cover.out -count=1
total=$(go tool cover -func=cover.out | awk '/^total:/ {gsub(/%/,"",$3); print $3}')
echo "total statement coverage: ${total}%"
# Ratchet, not a target. Windows-only packages (collectors, regf,
# lsass, winutil) cannot be exercised on the Linux runner, so the
# whole-tree number stays modest; this floor exists to stop it going
# *down*. Raise it whenever coverage improves — never lower it to
# make a red build green.
min=35
# Ratchet, not a target. The remainder is code that needs something
# this runner does not have: a SYSTEM/browser-user token (the DPAPI
# and app-bound-key collection backends), a live LSASS, a real Chrome
# or Edge profile, a spawning schtasks.exe / powershell.exe, or a
# domain to talk to. This floor exists to stop coverage going *down*
# — raise it whenever it improves, never lower it to green a build.
min=42
if awk "BEGIN {exit !(${total} < ${min})}"; then
echo "::error::coverage ${total}% is below the ${min}% floor"
exit 1
Expand Down Expand Up @@ -87,6 +88,100 @@ jobs:
- name: unit tests (platform-neutral packages)
run: go test ./internal/token ./internal/audit ./internal/ui ./internal/server

# Fuzzing on every push/PR. The packages exercised here eat untrusted input —
# registry hives, KDC replies, payload envelopes — inside a process that holds
# an elevated token, so a panic is a real availability bug, not a test
# artefact. Runs are short and bounded: this is a smoke gate, not a soak. Any
# crasher is written to testdata/fuzz/ and fails the job.
#
# One target per invocation: `go test -fuzz` matches a single target.
fuzz-smoke:
name: fuzz smoke (parsers)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- name: fuzz regf hive parser
run: go test -run='^$' -fuzz=FuzzRegf -fuzztime=30s ./internal/audit
- name: fuzz krb response parser
run: go test -run='^$' -fuzz=FuzzParseRep -fuzztime=30s ./internal/krb
- name: fuzz payload envelope
run: go test -run='^$' -fuzz=FuzzUnpack -fuzztime=30s ./internal/research
- name: fuzz obfuscator round-trip
run: go test -run='^$' -fuzz=FuzzMaskRoundTrip -fuzztime=30s ./internal/obfuscate
- name: fuzz persist name sanitizer
run: go test -run='^$' -fuzz=FuzzSanitizeName -fuzztime=30s ./internal/persist

# Static analysis (gosec) of exactly the code that ships. GOOS is pinned to
# windows even though the runner is Linux, because the entire
# security-sensitive surface lives in the windows-only packages; on linux the
# scan would silently see a third of the tree.
#
# The scan runs at full strength bar two rules excluded on the command line,
# each because every instance is deliberate and re-reviewing adds no signal:
# * G103 (use of unsafe) — the tool *is* an unsafe.Pointer/syscall
# bridge; 37 sites, all hand-reviewed.
# * G104 (unhandled error) — 14 sites, every one a cleanup path (deferred
# Close on a handle we are finished with).
# Every other finding was either fixed (file modes 0755/0644 -> 0700/0600) or
# suppressed inline as `//#nosec <rule> -- <reason>`, so the reason travels
# with the code: 47 suppressions, mostly protocol-mandated weak crypto
# (DES/RC4/MD4 per MS-SAMR, MS-NLMP, RFC 4757) and width narrowing in the
# binary parsers. `gosec ./...` without -exclude lists them all.
#
# Known blind spot: go/types cannot type-check a bodyless function, so gosec
# fails to build SSA for internal/research (EnergyFlow is implemented in
# energyflow_amd64.s) and the taint rules skip that one package. The job
# asserts that this is the *only* tolerated error — a new type error would
# likewise disable analysis silently, so it fails the build too.
sast:
name: gosec (static analysis)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
# Installed with the *runner's* GOOS. Setting GOOS=windows here would make
# this step cross-compile gosec to a PE binary (gosec.exe) that the Linux
# runner cannot execute; it is scoped to the scan step below instead.
- name: install gosec
run: go install github.com/securego/gosec/v2/cmd/gosec@latest
- name: gosec
env:
GOOS: windows
GOARCH: amd64
run: |
# setup-go does not put $(go env GOPATH)/bin on PATH, so resolve the
# binary explicitly rather than relying on `gosec` being found.
GOSEC="$(go env GOPATH)/bin/gosec"
# -no-fail: gosec exits non-zero on the tolerated assembly type error
# even with no findings; the gates below decide pass/fail instead.
"$GOSEC" -no-fail -fmt=json -exclude=G103,G104 ./... > gosec.json
# A scan that parsed nothing must not read as a clean scan.
files=$(jq '.Stats.files' gosec.json)
if [ "${files:-0}" -eq 0 ]; then
echo "::error::gosec analysed 0 files — the scan is vacuous, not clean"
exit 1
fi
jq -r '"gosec stats: files=\(.Stats.files) lines=\(.Stats.lines) nosec=\(.Stats.nosec) found=\(.Stats.found)"' gosec.json
issues=$(jq '.Issues | length' gosec.json)
if [ "$issues" -ne 0 ]; then
jq -r '.Issues[] | "::error file=\(.file),line=\(.line)::\(.rule_id) \(.details)"' gosec.json
echo "::error::gosec reported ${issues} finding(s)"
exit 1
fi
if jq -r '."Golang errors"[]?[]?.error' gosec.json \
| grep -v '^# command-line-arguments$' \
| grep -qv 'missing function body'; then
echo "::error::unexpected gosec type error — a package was not fully analysed"
jq -r '."Golang errors"[]?[]?.error' gosec.json
exit 1
fi
echo "gosec: no findings"

# Reachability-aware vulnerability scanning: govulncheck reports only the
# advisories your code can actually reach, so its verdict is actionable
# rather than a noisy list of every CVE in the module graph. It exits
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ obj/
# Go test coverage profile (produced by the CI coverage gate)
cover.out

# gosec JSON report from a local `gosec -fmt=json -out=gosec.json ./...`
gosec.json

# goreleaser output (local --snapshot builds; the CI release uploads these
# to the GitHub Release instead of the tree)
dist/
Loading
Loading