/proc · /sys · /boot — audited
Kernel hardening decays.
kspect notices.
Audit the running kernel's security posture — sysctls, boot parameters, kconfig, modules, CPU mitigations, LSMs — with a fix attached to every finding. Snapshot a baseline, fail CI on drift. One static binary, zero dependencies.
docker run --rm -v /:/host:ro ghcr.io/5h4rk-lab/kspect scan --root /host
docs/ARCHITECTURE.md
Four packages. One direction of dependency.
Collectors know nothing about rules; rules know nothing about the filesystem; the engine joins them; reporting only formats. Every seam is independently testable — hover the diagram.
Collect
Read-only, size-capped reads of kernel interfaces, resolved against --root — the same code audits a live host, a container-mounted host, or an offline image.
Evaluate
48 rules × collected facts, with any/all combinators and three-state results. Missing data is unknown, never a fabricated failure.
Report & gate
Terminal table, JSON, or SARIF for GitHub code scanning. --fail-on sets the severity gate; exit codes are a stable contract.
Baseline & diff
Snapshot posture after hardening; kspect diff exits 2 on any deviation — from cron, a systemd timer, or fleet tooling.
internal/engine — three-state evaluation
Missing data is never a failure.
Fleets are heterogeneous: kernels differ in version, config exposure, and the privileges kspect runs with. Tools that fail on what they can't see get uninstalled in a week. kspect's answer is a third state.
Verified in place
The value was read from the kernel and satisfies the check. Sorted last in output — you review problems, not confirmations.
Verified violation
The value was read and violates the control. Every failure carries the observed value, the expectation, an attacker-centric rationale, and a copy-pasteable fix.
Couldn't verify
The key doesn't exist on this kernel, or the source is unreadable at this privilege. Reported — reduced visibility stays visible — but it never trips the CI gate.
Why this matters: kernel.io_uring_disabled doesn't exist before Linux 6.6. A binary tool reports that as FAIL on every older kernel in your fleet — noise that erodes trust. kspect reports unknown, keeps it out of the gate, and still counts it in the summary.
README.md#why-kspect
Built to be wired in, not run once.
Runtime + build-time, one scan
Sysctls, boot parameters, kernel config, loaded modules, CPU vulnerability files, active LSMs, and lockdown state — the config the kernel was built with and the state it's running in.
internal/factsPolicy as code
Rules are reviewable JSON. Layer an org ruleset over the builtins: override severity, disable with a documented reason, add your own controls — all diffable in a PR.
examples/custom-rules.jsonSARIF for code scanning
Kernel posture regressions land in the same GitHub review surface as application findings — with per-rule help text and severity mapping. Security teams and platform teams see the same thing.
kspect scan --format sarifBaseline & drift
Snapshot the full posture after hardening. Any later deviation — a sysctl reset by an upgrade, a module loaded overnight — is a typed change and exit code 2.
kspect baseline · kspect diffEvery finding is actionable
Observed value, expected value, what an attacker gains, and the exact command that fixes it. "Best practice" is not a rationale in this ruleset.
internal/rules/builtin.jsonZero dependencies
Pure Go stdlib. A 2 MB static binary you can scp anywhere, run from a FROM scratch container, and audit in one sitting. Fleet-privileged tools deserve a minimal supply chain.
examples/ — captured output, not mockups
One tool, four outputs.
Everything below is genuine kspect output from the repository's intentionally-weak test fixture.
$ kspect scan --root testdata/rootfs-weak kspect scan — kernel 5.15.0-generic (x86_64) FAIL HIGH KSPECT-CMDLINE-001 CPU vulnerability mitigations not globally disabled observed: cmdline mitigations = "off" expected: cmdline mitigations != off fix: Remove mitigations=off from the kernel command line and reboot. FAIL HIGH KSPECT-MITIG-001 No unmitigated CPU vulnerabilities observed: meltdown: Vulnerable; spectre_v2: Vulnerable, IBPB: disabled, STIBP: disabled fix: Update the kernel and CPU microcode; remove any mitigation-disabling boot parameters. FAIL HIGH KSPECT-SYSCTL-003 Unprivileged BPF disabled observed: sysctl kernel.unprivileged_bpf_disabled = "0" fix: sysctl -w kernel.unprivileged_bpf_disabled=1 … 28 more findings … Summary: 48 checks — 31 fail, 4 pass, 13 unknown (8 high, 15 medium, 8 low)
$ kspect scan --format json | jq '.findings[0]' { "rule_id": "KSPECT-CMDLINE-001", "title": "CPU vulnerability mitigations not globally disabled", "severity": "high", "status": "fail", "observed": "cmdline mitigations = \"off\"", "expected": "cmdline mitigations != off", "rationale": "mitigations=off disables every hardware vulnerability mitigation (Spectre, Meltdown, MDS, ...), re-exposing the host to well-understood cross-boundary attacks.", "remediation": "Remove mitigations=off from the kernel command line and reboot.", "tags": ["cmdline", "cpu", "server", "container-host", "workstation"], "refs": ["https://docs.kernel.org/admin-guide/kernel-parameters.html"] }
$ kspect scan --format sarif | jq '.runs[0].tool.driver.rules[0]' { "id": "KSPECT-CMDLINE-001", "shortDescription": { "text": "CPU vulnerability mitigations not globally disabled" }, "fullDescription": { "text": "mitigations=off disables every hardware vulnerability mitigation (Spectre, Meltdown, MDS, ...) ..." }, "help": { "text": "Remove mitigations=off from the kernel command line and reboot." }, "properties": { "severity": "high", "tags": ["cmdline", "cpu", "server", "..."] } } # SARIF 2.1.0 — upload with github/codeql-action/upload-sarif
$ kspect baseline /var/lib/kspect/golden.json # once, after hardening kspect: baseline written (912 sysctls, 141 modules, kernel 6.8.0-generic) $ kspect diff /var/lib/kspect/golden.json # hourly, from a systemd timer Drift detected: 1 change(s) since baseline 2026-07-02T01:18:18Z ~ sysctl kernel.kptr_restrict "2" -> "0" $ echo $? 2 # drift → alert
docs/THREAT_MODEL.md
The threat model goes both ways.
What kspect defends against — and what a fleet-privileged auditor must defend in itself.
What the ruleset targets
- Local privilege escalation via kernel bugs — closes reachability (unprivileged eBPF, io_uring, userfaultfd, DCCP/RDS/TIPC/SCTP autoload) and exploitation aids (pointer leaks, disabled KASLR, NULL-page mapping, slab determinism).
- Cross-boundary hardware attacks — reads the kernel's own verdict in
/sys/…/vulnerabilitiesand catchesmitigations=off-class kill switches on the command line. - Kernel integrity after root — module signing, lockdown, kexec restrictions,
/dev/memaccess controls that turn a root compromise into a rootkit. - Hardening decay — baseline + drift turns "we hardened it last quarter" into a continuously verified invariant.
- Classic local attacks — /tmp link races, setuid core-dump leaks, same-user ptrace injection, on-host network hygiene.
kspect's own attack surface
- Strictly read-only. No writes, no network, no syscalls beyond open/read/stat. Remediation strings are printed, never executed — an auditor that runs strings from policy files is an RCE feature.
- Hostile input assumed. On a compromised host,
/procis attacker-influenced: reads are size-capped, parsers are non-recursive, observed values are escaped before terminal output. - Policy files can't execute. Rulesets and baselines are schema-validated JSON decoded into typed structs; the worst a tampered file yields is a wrong report.
- Minimal supply chain. Zero third-party modules; releases ship SHA-256 checksums; the container is
FROM scratch, runs as nobody, mounts the host read-only. - Honest limits. A kernel-resident attacker can lie to any on-host auditor. kspect claims point-of-audit integrity, not post-compromise forensics.
internal/rules/builtin.json — 48 rules by kernel surface
Where the checks look.
kspect audits configuration through stable kernel interfaces — no probes injected, no kernel modules, nothing loaded into ring 0.
The mitigations rule is one rule that fans out across every entry the kernel publishes — Spectre variants, Meltdown, MDS, Retbleed, and whatever your kernel version knows about.
measured, not estimated
Fast enough to run everywhere, always.
kspect reads a few hundred small virtual files and compares strings. There is no daemon, no database, no background agent.
README.md#install
Install in one line.
Root gives complete coverage; unprivileged runs still work — inaccessible sources simply report unknown.
curl -LO https://github.com/5h4rk-lab/kspect/releases/latest/download/kspect_linux_amd64 chmod +x kspect_linux_amd64 && sudo mv kspect_linux_amd64 /usr/local/bin/kspect
go install github.com/5h4rk-lab/kspect/cmd/kspect@latest
# audits the host through a read-only bind mount; image is FROM scratch docker run --rm -v /:/host:ro ghcr.io/5h4rk-lab/kspect scan --root /host
Quick start
$ kspect scan # audit, human-readable $ kspect scan --fail-on high # CI gate on high severity $ kspect scan --tags container-host --format sarif $ kspect baseline golden.json # snapshot after hardening $ kspect diff golden.json # exit 2 on any drift $ kspect rules --rules org.json # show effective policy
Profiles & policy
Scope a scan with tags — server, container-host, workstation, network, kspp — and layer org policy over the builtins with --rules: same rule ID overrides, "disabled": true suppresses with a documented reason, new IDs append.
Sources: sysctl, kconfig, cmdline, module, mitigation, securityfs. Operators: equals, one_of, contains, present, absent, min, max and negations.
examples/ — four deployment patterns
Where it earns its keep.
CI gate for VM & node images
Fail the image build if the kernel posture regresses; surface findings in GitHub code scanning.
- run: kspect scan --tags container-host --fail-on high \ --format sarif > kspect.sarif - uses: github/codeql-action/upload-sarif@v3
Fleet drift monitoring
A hardened systemd timer runs hourly; exit 2 fails the unit and lands in your existing alerting.
ExecStart=/usr/local/bin/kspect diff /var/lib/kspect/golden.json
# ProtectSystem=strict · PrivateNetwork=yes · CapabilityBoundingSet=
Incident-response triage
One static binary, no install, no network. Is this box in the state we hardened it to? Did anything change since the golden baseline?
$ scp kspect ir-host: && ssh ir-host ./kspect diff golden.json
Offline image auditing
Point --root at a mounted image or forensic copy to audit boot-time posture without booting it.
$ kspect scan --root /mnt/image --tags kspp
docs/DESIGN_DECISIONS.md — D-1
Different tools, different jobs.
kspect doesn't replace runtime detection, and runtime detection doesn't audit posture. Here's the honest map.
| capability | kspect | kernel-hardening-checker | Lynis | Falco / Tetragon |
|---|---|---|---|---|
| Runtime state (sysctl, cmdline, modules, mitigations) | yes | partial | partial | — |
| Kernel build config (kconfig) | yes | yes — deepest coverage | — | — |
| Baseline snapshots & drift detection | yes | — | — | — |
| Three-state results (unknown ≠ fail) | yes | — | — | n/a |
| SARIF / code-scanning integration | yes | — | — | — |
| Policy-as-code overrides in reviewable files | yes | partial | profiles | rules |
| Runtime behavior detection (events, syscalls) | no — by design | — | — | yes — this is their job |
| Scope beyond the kernel (services, PAM, filesystems…) | no — kernel only | — | yes, very broad | — |
| Dependencies on the audited host | none — static binary | Python | shell + tools | agent/daemon |
Run kspect for posture and drift; keep kernel-hardening-checker if you want maximal kconfig depth; keep Lynis for whole-system breadth; pair with Falco or Tetragon for runtime telemetry. They compose.
$ man kspect — frequently asked
FAQ
Does kspect need root?
No, but root gives complete coverage — some sysctls and all of securityfs are root-only. Unprivileged runs degrade gracefully: unreadable sources report unknown and never trip the gate, and the summary shows exactly how much visibility you had.
Will it change anything on my system?
Never. kspect is strictly read-only — no writes, no network, no syscalls beyond open/read/stat. Fixes are printed as text for you to apply; they are never executed. This is a hard design line, not a default.
How does it avoid false positives?
Three-state evaluation. A check only fails when the value was actually read from the kernel and actually violates the rule. Keys absent on your kernel version, or sources you lack privilege to read, report unknown. Advisory rules are severity info, render as NOTE, and never gate by default.
Which distributions and kernels are supported?
Any Linux that exposes the standard interfaces — /proc/sys, /proc/cmdline, /proc/modules, and sysfs — which is effectively all of them. Kconfig is read from /proc/config.gz or /boot/config-* when present; when it isn't, those eleven rules report unknown rather than failing.
Why JSON rules instead of YAML?
Two reasons: parsing YAML would require kspect's first third-party dependency, and YAML's implicit typing (no → false) is a footgun in security policy files. The cost — no comments — is mitigated by rationale being a required schema field.
Is this a Falco/Tetragon alternative?
No. Those detect runtime behavior; kspect audits configuration. A hardened posture reduces what an attacker can reach, and runtime detection catches what they do anyway — you want both, and they don't overlap.
Can I add my organization's own rules?
Yes — --rules org.json layers your ruleset over the builtins. Same ID overrides (retune severity, or disable with a documented reason); new IDs append. The whole policy lives in a file your team reviews in pull requests.
Can a compromised host fool kspect?
A kernel-resident attacker can lie to any on-host auditor — kspect's threat model says so explicitly. Its claim is point-of-audit integrity and drift detection, not post-compromise forensics. Defensive parsing (size caps, escaping) ensures hostile /proc content can corrupt at most the report, never the tool.
docs/ROADMAP.md
Where it's going.
v0.1 — shipped
48-rule audit across six kernel surfaces · three-state engine · baseline & drift · table/JSON/SARIF · policy overlays · scratch container · stable exit-code contract.
v0.2 — fleet ergonomics
NDJSON for log shippers · drift ignore-lists for expected churn · Prometheus textfile output so posture becomes a dashboarded metric · ruleset-to-Markdown docs generator.
v0.3 — deeper collection, still read-only
Installed-vs-running kernel divergence · modprobe.d blacklist awareness (not loaded vs cannot load) · Landlock/seccomp availability · per-container reachability analysis (RFC).
v0.4 — ecosystem
Signed ruleset updates decoupled from binary releases · CIS section mapping · deb/rpm/nix packaging · kubectl plugin and DaemonSet for cluster-wide node audits.
CONTRIBUTING.md
The ruleset is the product. Help build it.
A rule PR ships with fixture coverage, an attacker-centric rationale, and a false-positive analysis. Rules that fire on stock distro configs need strong justification — credibility is the whole value.
Read the docs
Architecture, the two-way threat model, and a living engineering log of decisions, rejected alternatives, and lessons learned.
Contribute a rule
Pick a control, write the JSON, extend both test fixtures, open a PR. The contribution guide walks through all three steps.
Report & discuss
Bugs and feature requests in issues; vulnerabilities in kspect itself privately via GitHub security advisories.