Skip to content

v0.16.0

Latest

Choose a tag to compare

@github-actions github-actions released this 06 Jul 14:09
1e3896c

InnerWarden 0.16.0

Added

  • GuardFall runtime coverage: find -delete bulk deletion in the data-destruction
    detector.
    The static check-command (above) blocks GuardFall when the agent
    proposes the destructive command directly, but a payload hidden in a repo
    (a Makefile/script the agent innocently runs) executes as a subprocess the
    static hook never sees. The sensor closes that gap because it inspects the REAL,
    already-shell-resolved argv the kernel exec'd - so r''m -rf /home/user is
    caught on its post-rewrite ["rm","-rf","/home/user"] form regardless of the
    obfuscation. The data_destruction_pattern detector now also fires on an
    unfiltered find <user-data-path> ... -delete (GuardFall class E, the bulk-delete
    tool a rm-watching blocklist misses), while leaving the ubiquitous filtered
    cleanup (find . -name '*.tmp' -delete), /tmp, and build-cache dirs silent - the
    same target discrimination the rm -rf path uses, so a malicious make test
    wiping /home/user fires while a legit make clean of target/ does not.

Added

  • GuardFall shell-rewrite defence in the agent-guard check-command engine.
    A text/regex blocklist that inspects the command an AI agent proposes is fooled
    because the shell REWRITES the command before running it (a class of attack the
    GuardFall research showed defeated 10 of 11 open-source coding agents). The
    check-command engine (threats::check_command, behind /api/agent/check-command
    and iw-guard check) now runs a pure, non-executing normalize_command pass that
    de-obfuscates the shell rewrites back to their real form before matching: empty-quote
    insertion (r''m -> rm), $IFS word-splitting (rm$IFS-rf), command substitution
    ($(echo rm), backticks), variable indirection (${x:-rm}), and backslash escapes
    (\r\m). It also adds destructive tools a rm-watching blocklist misses (dd of=,
    shred, install/cp from /dev/null, truncate -s 0, tar -C /) and a
    filter-aware find -delete check that blocks an unfiltered bulk delete
    (find /path -type f -delete) while leaving the ubiquitous filtered cleanup
    (find . -name '*.tmp' -delete) allowed. GuardFall classes A-E now resolve to
    deny; the false-positive benchmark ceiling holds (1/18). The normalizer never
    spawns a shell or evaluates input and is length- and pass-bounded against DoS.

Highlight: Windows support (Phantom tier)

InnerWarden now runs on Windows, not just Linux/macOS. Two tiers ship: the
per-user AI-agent guardrail (iw-guard: check / serve / enforcing MCP proxy /
one-command Claude Code hook), and the elevated full host tier (install.ps1 -Full: the signed sensor+agent+ctl trio, boot-start Scheduled Tasks, Windows Event
Log (ETW) telemetry -> the 82 detectors -> cloud-AI triage -> dashboard -> Windows
Firewall containment, monitor-only by default). This is the macOS-parity light tier
(spec 085); the kernel EDR (eBPF, Execution Gate) stays Linux-only. Minor version
bump: a new operating-system tier is a feature, not a patch.

Added

  • install.ps1 -Full installs the Mac-parity Windows host tier (spec 085 Phantom Phase 5). The default install.ps1 still installs only the iw-guard guardrail per-user; the new elevated -Full mode downloads the signed trio (innerwarden-{sensor,agent,ctl}-windows-x86_64.exe, published by the new build-release-windows-full release job), installs to %ProgramFiles%\InnerWarden with state under %ProgramData%\InnerWarden, writes a Windows monitor-only config (ETW + integrity collectors on, Linux collectors off, [responder] enabled=false dry_run=true), locks the state dir to Administrators/SYSTEM, and registers two boot-start SYSTEM Scheduled Tasks (InnerWarden\innerwarden-{sensor,agent}, the names ctl's systemd.rs restart/status route to) with KeepAlive semantics (ExecutionTimeLimit=0 to defeat the 72h auto-kill, 1-min repetition, RestartCount). So a Windows box can now download, install, and boot-start the full light tier: ETW host telemetry -> the 82 detectors -> cloud-AI triage -> dashboard, plus firewall containment via the block_ip_windows skill. A powershell-parse CI job AST-parses install.ps1 on every change. Light Scheduled Task, not true SCM (the plain console exes have no ServiceMain); eBPF/Execution Gate stay Linux-only.
  • Windows now ships the AI-agent guardrail: iw-guard, a signed, SLSA-attested .exe per release. The guardrail half of InnerWarden (screen an agent's shell command / MCP tool call for danger) is OS-agnostic Rust, so it now runs the same on Linux, macOS, and Windows without the Linux host-EDR (eBPF/sensor/Execution Gate stays Linux-only). A new thin crate crates/guard-cli (bin iw-guard) drives the already-Windows-clean agent-guard engine with three subcommands: iw-guard check "<cmd>" (verdict JSON, exits 1 on deny so a PreToolUse hook can gate), iw-guard serve (plain-HTTP POST /api/agent/check-command on loopback for an MCP wrapper), and iw-guard proxy --mode guard -- <server> (the enforcing stdio MITM that blocks a disallowed tools/call inline). iw-guard install claude-code wires a fail-closed PreToolUse:Bash hook into Claude Code in one command (offline, in-process): the hook runs iw-guard hook, which reads the tool call on stdin, screens the command locally, and blocks it (exit 2) on a deny (--block-review also blocks review). It depends only on agent-guard (plus its mcp_proxy): no sensor, jemalloc, systemd, /proc, or TLS on the check-command path. The new build-release-windows job publishes iw-guard-windows-x86_64.exe (+ build-only aarch64) with a lowercase SHA-256 sidecar, an Ed25519 .sig (same scheme as Linux/macOS), and a Sigstore provenance bundle. A windows-guard CI job builds + smoke-tests the real .exe (check + serve) on a windows-latest runner on every guardrail-touching PR. install.ps1 fetches, verifies, and installs it. No sensor/agent/detector-count change on Linux.
  • innerwarden audit anchor / innerwarden audit verify — the free CLI for the decision-log external anchor. Wraps the Store::compute_anchor / verify_against_anchor primitives (shipped 0.15.34) in an operator-facing command: audit anchor prints a compact commitment to the decision log's current tip (seq + tip row_hash + count) that the operator records OUTSIDE this host; audit verify --file <anchor.json> (or --anchor '<json>') later proves the live log still contains that exact committed history — verdict Intact / Truncated / Rewritten. This catches the tamper class the internal hash chain cannot: whole-log deletion or a rollback-and-regrow from a fresh (internally-consistent) root, which a rebuilt chain still passes. verify exits non-zero on Truncated/Rewritten so it can gate CI or a monitoring probe; --json on either subcommand emits machine-readable output, and anchor --json round-trips straight into verify --anchor. Reads the same SQLite store as system chain-break, so it needs no agent restart. The paid innerwarden-config-sign audit adds an Ed25519 signature so the anchor is itself tamper-evident off-host; this free form is unsigned (trust rests on where the operator stores it). CLI-only; no sensor/agent/detector-count change.

Security

  • suspend-user-sudo no longer grants the innerwarden user a write-anything-into-/etc/sudoers.d/ primitive — the privileged step is now a hard-coded helper subcommand, closing a self-inflicted privilege-escalation path. The response that temporarily revokes a user's sudo has to drop a root-owned file under /etc/sudoers.d/, but the agent runs unprivileged, so it shells out through sudo. The old grant was innerwarden ALL=(ALL) NOPASSWD: /usr/bin/install … <a /tmp file> /etc/sudoers.d/… — i.e. it let the innerwarden user install an arbitrary-content file into sudoers.d, which is full root (write innerwarden ALL=(ALL) NOPASSWD: ALL). Worse, the generated grant's path patterns did not even match what the skill actually ran, which is how a box ended up with the over-broad install * /etc/sudoers.d/* wildcard just to make suspension work (the exact rule innerwarden scan now flags). The fix removes the arbitrary-content primitive entirely: two hidden helper subcommands — innerwarden __sudo-suspend --user <u> --expires <ts> / __sudo-restore --user <u>generate the drop-in themselves (a fixed deny-all rule, root/uid-0 refused, visudo -cf-validated, atomically installed 0440 root:root), and the sudoers grant is narrowed to only those two subcommands. The only attacker-influenceable input is a username, and the only content that can ever be written is a deny-all rule, so the worst a compromised caller can do through the grant is deny sudo to a non-root user — fail-safe, not escalation. Filename sanitisation, username validation, and the rule body now live once in innerwarden_core::sudo_guard (shared by the agent skill and the root helper) so "what the agent recorded" and "what root wrote" can never diverge. Existing installs migrate to the narrow grant on the next harden / capability re-enable; innerwarden scan (0.15.34) already surfaces any residual over-broad drop-in. Needs a Linux lab pass of the live sudo grant + visudo before deploy. Anchored by tests in all three crates (core: validation/sanitisation/render; ctl: root/root-refusal + 0600 temp + grant-shape asserts; agent: dry-run/TTL/validation/cleanup).

Install / upgrade (Linux, toolchain-free, signed binaries)

curl -fsSL https://innerwarden.com/install | sudo bash
# already installed:  sudo innerwarden upgrade --yes

Every binary below is signed (Ed25519 + Sigstore bundle). Docs: https://github.com/InnerWarden/innerwarden/wiki · Site: https://www.innerwarden.com

What's Changed

  • chore: adopt the Developer Certificate of Origin by @maiconburn in #1204
  • docs(readme): normalize brand, add kernel-gate line and DCO note by @maiconburn in #1205
  • deploy/observability: Grafana + Prometheus fleet dashboard by @maiconburn in #1207
  • observability: per-agent cost & tokens dashboard row by @maiconburn in #1208
  • observability: incident investigation layer (Loki + Alloy) by @maiconburn in #1209
  • agent: tenant in agent-guard-events (per-tenant incident investigation) by @maiconburn in #1210
  • observability: per-tenant command review + rogue-agent signal (P3) by @maiconburn in #1211
  • observability: reusable multi-tenant demo scenario generator by @maiconburn in #1212
  • sensor: gate reverse_shell + data_exfil_ebpf behind cloud guest-agent check (Azure ExtHandler FP flood) by @maiconburn in #1213
  • observability: tenant on agent_guard incidents + SQLite->Loki bridge by @maiconburn in #1214
  • agent-guard: OWASP Agentic Top 10 coverage (reason chain + ASI07 redaction + ASI09 breaker) by @maiconburn in #1215
  • docs: remove em-dashes from OWASP coverage doc by @maiconburn in #1216
  • perf: cache agent-guard regexes + stream growing jsonl logs (native, zero behaviour change) by @maiconburn in #1217
  • perf: drop per-event allocations in the eBPF ring reader + stream /proc/kallsyms by @maiconburn in #1218
  • perf: Arc-cache the container identity (cache hit = refcount bump, not a per-event clone) by @maiconburn in #1219
  • fix: eliminate two parallel-test flakes (smoke-test transient spawn + registry global counter) by @maiconburn in #1221
  • docs: correct the test-count badge (7900+ -> 9800+) by @maiconburn in #1220
  • fix: sensor x86_64 build (container_id borrow bound &&str, not &str) by @maiconburn in #1222
  • ci: add ebpf-build job to type-check the sensor with deploy features (close the #1222 gap) by @maiconburn in #1223
  • fix(sensor): surface a host-blind eBPF sensor instead of reporting active by @maiconburn in #1224
  • fix(installer): make agent.env EnvironmentFile optional (fresh install couldn't start the agent) by @maiconburn in #1225
  • fix(store): stop "database is locked" ERROR spam on first start (establish WAL before the pool) by @maiconburn in #1226
  • feat(guard-cli): iw-guard - the AI-agent guardrail as a cross-platform (Windows-ready) binary by @maiconburn in #1227
  • feat(guard-cli): iw-guard proxy - the enforcing MCP guard (Windows-ready) by @maiconburn in #1228
  • feat(release): ship iw-guard.exe - signed, attested Windows guardrail per release by @maiconburn in #1229
  • feat(guard-cli): iw-guard install claude-code + hook - one-command PreToolUse guard by @maiconburn in #1230
  • feat(windows): Phantom Phase 0 - compile-unlock sensor+agent+ctl on windows-msvc by @maiconburn in #1231
  • feat(windows): Phantom Phase 1 - ETW/Event-Log telemetry collector by @maiconburn in #1232
  • feat(windows): Phantom Phase 2 - block_ip via Windows Firewall skill by @maiconburn in #1233
  • feat(windows): Phantom Phase 4 - ctl Scheduled-Task service routing by @maiconburn in #1234
  • feat(release): build-release-windows-full - signed, attested Windows trio (Phase 5) by @maiconburn in #1237
  • feat(windows): Phantom Phase 5 - install.ps1 -Full (trio install + boot-start) by @maiconburn in #1238
  • release: prep v0.16.0 (Windows/Phantom tier) by @maiconburn in #1239
  • release: promote development to main (v0.16.0 - Windows/Phantom tier) by @maiconburn in #1240
  • fix(release): locate openssl on Windows sign steps (v0.16.0-rc.1 fix) by @maiconburn in #1241
  • release: promote development to main (v0.16.0-rc.2 openssl fix) by @maiconburn in #1242
  • feat(agent-guard): GuardFall shell-rewrite defence in check-command by @maiconburn in #1243
  • release: promote development to main (v0.16.0 GuardFall defence) by @maiconburn in #1244
  • feat(sensor): GuardFall runtime coverage - find -delete bulk deletion by @maiconburn in #1245
  • fix(sensor): capture execve argv in-kernel (GuardFall runtime, reliable for short-lived procs) by @maiconburn in #1246
  • release: promote development to main (v0.16.0 GuardFall runtime + argv capture) by @maiconburn in #1247

Full Changelog: v0.15.34...v0.16.0