kspect v0.1 Star on GitHub

/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
Apache-2.0Go, stdlib onlyread-only by designSARIF 2.1.0
root@prod-node-04 — kspect
18 fail 19 pass 11 unknown a stock cloud kernel, unhardened
0
third-party dependencies — the supply chain is this repo and the Go toolchain
48
KSPP/CIS-informed checks, each with rationale, fix, and references
84.9%
test coverage, race detector on, fixture-based end-to-end suite
0/1/2/3
stable exit-code contract, pinned by test — CI can depend on it

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.

/proc/sys/** /proc/cmdline /proc/config.gz · /boot /proc/modules /sys/…/vulnerabilities /sys/kernel/security facts Collect(root) rules builtin ⊕ custom engine pass·fail·unknown report table·json·sarif baseline snapshot · diff exit codes: 0 clean · 1 findings · 2 drift · 3 error

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.

PASS

Verified in place

The value was read from the kernel and satisfies the check. Sorted last in output — you review problems, not confirmations.

FAIL

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.

UNKNOWN

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/facts

Policy 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.json

SARIF 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 sarif

Baseline & 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 diff

Every 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.json

Zero 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.

go.mod — no requires

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)

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/…/vulnerabilities and catches mitigations=off-class kill switches on the command line.
  • Kernel integrity after root — module signing, lockdown, kexec restrictions, /dev/mem access 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, /proc is 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.

sysctl/proc/sys/** 23 kconfig/proc/config.gz · /boot/config-* 11 boot cmdline/proc/cmdline 6 modules/proc/modules 4 LSM · lockdown/sys/kernel/security 3 CPU mitigations/sys/devices/system/cpu/vulnerabilities 1

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.

28 ms
full 48-check scan of a live kernel, including a complete /proc/sys walk
2.0 MB
static stripped binary — fits in an initramfs, ships in a scratch container
0 daemons
run on demand: CI step, cron job, systemd timer, or one-off during IR triage
2,250 LOC
small enough to audit before you grant it fleet-wide root

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

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/custom-rules.json

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)yespartialpartial
Kernel build config (kconfig)yesyes — deepest coverage
Baseline snapshots & drift detectionyes
Three-state results (unknown ≠ fail)yesn/a
SARIF / code-scanning integrationyes
Policy-as-code overrides in reviewable filesyespartialprofilesrules
Runtime behavior detection (events, syscalls)no — by designyes — this is their job
Scope beyond the kernel (services, PAM, filesystems…)no — kernel onlyyes, very broad
Dependencies on the audited hostnone — static binaryPythonshell + toolsagent/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.

github.com/5h4rk-lab/kspect/docs →

Contribute a rule

Pick a control, write the JSON, extend both test fixtures, open a PR. The contribution guide walks through all three steps.

CONTRIBUTING.md →

Report & discuss

Bugs and feature requests in issues; vulnerabilities in kspect itself privately via GitHub security advisories.

Issues → · SECURITY.md →