All articles

White Rabbit: a read‑only server security audit copilot for Claude Code

7 min read
Security AI Claude Code

I run a production server plus a handful of projects in development, and all of it needs a periodic checkup: how SSH is configured, what’s exposed to the internet, who’s already knocking in the logs, which installed packages carry known CVEs. A typical scanner answers with a list of hundreds of “vulnerabilities” and no priorities — and you’re the one left deciding what’s actually dangerous and what’s noise. I didn’t want a scanner. I wanted a copilot that says: “fix these three today, the rest can wait, and here’s why.” That’s how White Rabbit came to be.

The name is from “follow the white rabbit” — chasing what’s hiding in the logs and configs.

What it is

White Rabbit is a plugin for Claude Code. Not a standalone binary — a set of playbooks, reference catalogs, collectors and guardrails that turns a Claude session into an auditor of your infrastructure. Everything runs through a single /wr command routed by domain:

  • /wr server — SSH configuration, firewall, listening ports, privileged accounts, persistence mechanisms, Docker settings.
  • /wr logs — SSH/auth log analysis: brute force, username enumeration, successful logins from addresses that were brute-forcing earlier.
  • /wr web — nginx and Caddy HTTP access logs: probes of sensitive files, scanners, login-form brute force, payloads in request paths.
  • /wr cve — OS package checks against OSV.dev, prioritized with CISA KEV and EPSS.
  • /wr all — everything at once, plus cross-surface correlation and a delta against the previous run, delivered as a single sorted feed of findings.

The key word is orchestrator, not scanner. White Rabbit invents no engines of its own: data is collected with native commands, and all the value is in how that data gets interpreted and prioritized.

Zero-install

Nothing gets installed on the audited server. Claude connects over SSH and takes the picture with the target system’s stock utilities: ss for ports, journalctl for logs, dpkg or rpm for packages, config reads, and whatever firewall CLI is available. One collector gathers the whole state dump in a single pass:

ssh user@host 'bash -s' < scripts/collect/server_snapshot.sh

That’s faster and more reproducible than thirty separate commands, and the collectors themselves contain only read operations — an extra constraint baked in at the design level. The streaming cap in the web-log collector (200,000 lines by default) exists because an access log of 1.3 million requests once killed the SSH session; when input gets truncated, the report says so explicitly.

Secrets are never read: the collector captures the effective SSH configuration but not key material; shadow hashes and key contents are never printed — only the fact that they exist.

Read-only

Here’s the part I was building up to. Giving a model SSH access to a production server and hoping it won’t run a state-changing command is not security — it’s optimism. The model can err, misread the task, or run something that looks harmless but mutates the host. So read-only in White Rabbit is not a wish written into the prompt. It’s a PreToolUse hook that intercepts every Bash command before execution and blocks anything that fails the checks. The constraint is enforced by an executable layer, not by the model’s good will.

The hook’s first property: it fails closed. Can’t parse the input — block. No jq — block. Policy files missing — block.

# Fail closed if jq is unavailable: we cannot safely parse input, so block (read-only).
if ! command -v jq >/dev/null 2>&1; then
  echo "White Rabbit guard: jq not found — failing closed (read-only)." >&2
  exit 2
fi

Then every command passes three checks: does it match a forbidden mutation pattern (rm, chmod, kill, sed -i, apt install, iptables -F, and about seventy more), does it redirect output into a file, and is the first binary of every pipeline segment on the allowlist of read-only commands. Fail any one of them — blocked.

One non-obvious vector is handled separately. White Rabbit’s own analyzers (the correlator, the CVE scanner) run locally and have to be let through — but allowing them by filename would be dangerous: an attacker drops their own correlate.sh into a directory they control and gets execution. So they are allowed only by canonical path inside the plugin, not by filename. A same-named script anywhere else fails that check and falls under the general rules.

To be fair: read-only self-auditing is not a new idea — Lynis and plenty of vulnerability scanners work the same way. The difference here is that the no-mutation rule is enforced by the agent’s executable guardrail, not just by prompt discipline. That substantially cuts the risk of accidentally changing the host, though it doesn’t make the mechanism a formally proven sandbox.

Extending through markdown

The tool is built so its capabilities grow mostly without code. Every audit domain is a playbook skill in skills/: what to check, which commands collect the evidence, how to interpret it and how to score severity. Next to them sit reference catalogs in knowledge/: check sheets and a shared severity model the playbooks point to. Deterministic collection and correlation live in small shell scripts; the reasoning over the dump is Claude’s, guided by the playbook.

Adding a new check usually means extending the playbook and the catalog. For example, the knowledge that “a published Docker port bypasses ufw” is seven lines of markdown in knowledge/checks/docker.md: what to look for in docker ps output, which sections of the dump to cross-reference, why it’s dangerous and how to fix it. The brain of the tool is Claude Code; White Rabbit hands it the methodology and the deterministic collection tooling.

Prioritization

This is the main differentiator and the reason the whole thing exists. A raw list of findings is useless without an answer to “which of these is actually exploitable.” A few examples of how White Rabbit gets there.

CVEs are prioritized not by CVSS alone but with CISA KEV (the vulnerability is already being exploited in the wild) and EPSS (probability of exploitation). Only actionable findings make it into the report — the ones with a fixed version already released. A vulnerability irrelevant to a particular environment can be suppressed with VEX, with a stated justification.

A published Docker port is cross-referenced with the firewall: a container can be reachable from the outside even when ufw reports active — Docker inserts its own rules, and the traffic doesn’t necessarily traverse the INPUT chain. The operator believes the port is closed while it’s open to the whole internet — this case gets flagged explicitly.

Attacker IPs are matched across surfaces: if one address both brute-forced SSH passwords and scanned the web application, its priority goes up. A successful SSH login from an address that previously attacked the web app is a critical indicator of possible compromise, not just another line in a list. On top of that comes the delta against the previous run: every finding is marked [NEW], [UNCHANGED] or [RESOLVED], so you see not only the state but what changed.

Scope and limits

The layer that separates “fix these three today” from the noise is Claude’s own reasoning over the collected data. And here I have to be honest: that layer is non-deterministic. The collectors, correlators and CVE matching are covered by behavioral tests — 271 of them at the moment, running against fixture trees and stubbed utilities, no live host required — and I stand behind their output; triage quality depends on the model, and its verdict is a recommendation, not a sentence. The contents of logs and configs are potentially hostile data, too: a log line can masquerade as an instruction to the model, and it has to be treated accordingly.

And about maturity, plainly. This is version 0.1.0 from a single author. The core is done: server, log and web-traffic audit, OS package CVE checks, cross-surface correlation and an HTML report. Planned but not built yet: code audit (SAST and secrets detection), application dependency CVEs with reachability analysis, attacker IP enrichment, TLS and certificate checks, report delivery to Slack, scheduled runs. White Rabbit doesn’t replace a SIEM like Wazuh or your hosting provider’s platform defenses, and doesn’t claim to.

The niche it covers is narrow but real for me: come to your own server through Claude Code, get a prioritized picture in one pass, and leave the host state untouched — commands that fail the read-only check are blocked before they run. If you need continuous protection, that’s a job for other tools. If you need a one-off white-box audit of your own machine with clear priorities on the findings — that’s exactly what White Rabbit is for.

Repository: github.com/IvanShishkin/white-rabbit