| config | ||
| samples/demo-gates | ||
| schema | ||
| tests | ||
| ward | ||
| .gitignore | ||
| conftest.py | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
Ward — guardrail-integrity monitor
Ward answers one question, continuously and provably: are the enforcement guardrails that gate an AI agent's risky actions still exactly what the operator blessed — un-forged, un-drifted, and still wired in?
A capable AI agent runs behind a set of guardrails: pre-tool-use hooks that block a deploy without an operator approval, a planning-mode gate, shared classifier libraries, and the operator-only "write half" that mints an approval marker. Those guardrails are just files on a host. If one is silently edited, unwired, or deleted, the agent looks healthy while its safety net has a hole. Ward is the independent monitor that catches exactly that — without ever being able to weaken the thing it watches.
The core invariant — courier, never notary
Ward is read-only and holds no signing key. It enumerates and hashes the guardrail artifacts and compares live state against an operator-signed baseline — but it can neither change a guardrail nor mint a baseline. The operator, out-of-band and with a key the monitor never sees, is the notary; Ward is only the courier that recomputes live state and reports the diff.
That boundary is the whole design. A compromised monitor is a classic weak link:
if the watcher can bless its own baseline, an attacker who owns the watcher owns
the guardrails too. Ward closes that by construction — the private key lives off
the box, the monitor verifies against a pinned public key, and a
self-generated key + self-made signature is refused because it cannot match the
operator-anchored fingerprint (TOFU, the same trust model cosign / TUF /
SSH known_hosts accept).
How it works
Three stages, mapping to the modules below.
W1 — enumerate + canonically hash (ward/manifest.py).
Walk a declared set of guardrail artifacts (enforcement hooks, gate libraries,
marker writers, the operator write-half, and the settings hooks subtree that
wires them in) and emit a stable JSON manifest. Each artifact carries its
sha256, mode/uid/gid, symlink target, and a wired verdict (is it actually
referenced by the enforcement wiring, or an orphan?). The signable
manifest_digest is a sha256 over the canonical artifacts array only —
volatile metadata (timestamp, host) is excluded, so an unchanged host yields a
byte-identical digest every run. A missing artifact is recorded as a finding,
never a silent skip.
W2 — the sign ceremony (ward/ceremony.py, baseline_sign.py, baseline_bless.py).
The AI stages a baseline (builds the manifest, writes the detached digest,
inserts an inactive, unsigned row) and prints the exact one-liner the operator
runs in their own terminal to sign the digest. The AI then blesses it: it
verifies the operator's detached signature against the pinned public key and,
only on success, pins the key and flips the row to active. Every failure path —
wrong key, corrupt signature, unconfirmed fingerprint, a whole guardrail set
absent — refuses and leaves the trust anchor unpinned.
W3 — the fail-closed scan (ward/scan.py, comparator.py, verify.py).
The load-bearing order is non-negotiable: verify the baseline signature before
any comparison. A clean verdict is reachable only through a verified
signature — an unverified scan with zero drift is still unverified, never
clean. Silence is never clean. Once verified, the comparator diffs live vs
baseline artifact-by-artifact; a byte change on an enforcement hook or the
operator write-half is a CRITICAL, and a gate that vanished or an unblessed gate
that appeared is itself a finding.
Layout
| Path | Role |
|---|---|
ward/manifest.py |
W1 — gate enumerator + canonical, stable, signable hasher |
ward/trust.py |
pinned public keys (TOFU anchors); the only place a key becomes authoritative |
ward/verify.py |
signature verification with algorithm dispatch (ed25519 native; cosign wired) |
ward/baseline.py |
load the single active + blessed baseline; resolve pin + TOFU cross-check |
ward/comparator.py |
per-artifact live-vs-baseline byte/metadata diff |
ward/findings.py |
finding model, stable dedup key, regressed transition |
ward/scan.py |
the fail-closed orchestrator (verify-before-compare) |
ward/ceremony.py |
the W2 stage → bless sign ceremony |
ward/store.py |
DB-agnostic baseline row store (JSON-file stand-in for Postgres) |
ward/cli.py |
read-only JSON manifest emitter |
schema/*.sql |
illustrative Postgres schema (findings, baseline, per-run ledger) with the invariant encoded as CHECK constraints |
samples/demo-gates/ |
a small, synthetic guardrail tree the tests run against |
tests/ |
the W1/W2/W3 suite, including the fail-closed and forge-resistance proofs |
Run it
python -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/python -m pytest -q
# point Ward at your own guardrail tree and print the manifest:
WARD_GATE_ROOT=samples/demo-gates .venv/bin/python -m ward.cli
WARD_GATE_ROOT=samples/demo-gates .venv/bin/python -m ward.cli --digest
The test suite is self-contained: it builds every manifest from the bundled
samples/demo-gates tree and simulates drift by mutating a copy of the
manifest — no real file is ever touched, and no host's actual guardrail topology
is present in this repository.
About this repository
This is a curated, self-contained snapshot published as an engineering showcase.
The gate topology here is deliberately generic (WARD_GATE_ROOT + a demo tree);
a real deployment supplies its own set. The reusable, portfolio-relevant part is
the architecture: read-only integrity monitoring, an operator-signed baseline the
monitor cannot forge, canonical stable hashing, and a fail-closed
verify-before-compare scan.
License
AGPL-3.0-only — see LICENSE for the full text.
A commercial license is available for use outside the AGPL-3.0 terms — contact Info@Seglamater.com. (Formal commercial-license terms are being finalized.)
Versions previously published under the Business Source License 1.1 remain available under BSL-1.1.
Copyright © 2026 Seglamater Services LLC.