🛡️ Break-It Challenge is open — think a governed agent can escape the BPF-LSM floor, lock out the operator, or win an ALLOW that should be a DENY? Try to break it. Public credit for valid findings; it all reproduces locally. See
BREAK_IT_CHALLENGE.md.
Jinn Guard is an asynchronous, kernel-aware agent governance daemon designed to enforce safety constraints on autonomous AI agents before any tool execution is permitted. It evaluates agent actions using a keyword/intent heuristic plus an optional external AI semantic scorer, and processes them through a lifetime-anchored Z3 SMT solver pipeline — performing constraint checks on state transitions and risk ceilings against formalized compliance models before granting or denying execution authority.
Operating locally over high-throughput UNIX domain sockets, the platform binds user-space proxy validation with low-level eBPF kernel telemetry and namespace tracking to enforce zero-trust process isolation and anti-replay protection for governed cgroups.
Automated demo (Rust + eBPF): one legitimate request is allowed; real attacks are blocked live by the daemon.
bash scripts/demo.sh # or: bash scripts/demo.sh --autoA narrated dashboard that drives the real daemon: one legitimate request is allowed, seven real attacks are blocked live, the daemon's own metrics are read back, and the validated numbers + safety guarantees are walked through — plain enough for a non-technical audience. Nothing is mocked. Presenter notes and safety FAQ:
DEMO.md.
python3 scripts/validate/validate.py # attacks, determinism, audit-chain, tamper-proofA reproducible, dependency-free harness that drives the real daemon and lets you independently verify every claim: re-run for determinism, recompute the tamper-evident audit hash-chain yourself, corrupt the log and watch it get caught, and bring your own attack. See
scripts/validate/andscripts/validate/CLAIMS.md.
Every number below comes from running the real daemon on a real machine (AMD Ryzen 5 7520U, 8 cores, Linux 6.12), with each request driven through the complete decision pipeline — nothing is faked or stubbed. Measured 2026-06-12. Reproduce locally:
cargo bench --bench stress_bench # speed + throughput
cargo test --release --test swarm_attack # attack resistanceFull detail: BENCHMARKS-01.md.
One "decision" = a full security check (verify signature → replay/identity/intent/ quota gates → risk + Z3 math ceiling → audit). Across 10,000 requests:
| Half of requests finish under | 95% finish under | 99% finish under | Slowest |
|---|---|---|---|
| 257 µs (P50) | 366 µs (P95) | 463 µs (P99) | 1.9 ms |
In plain English: a complete security decision takes about a quarter of a millisecond.
| Load | Throughput | Errors |
|---|---|---|
| 10–500 agents at once | ~6,500 decisions/sec | 0 |
| Single client | ~3,640 decisions/sec | 0 |
| 5,000 mixed allow/deny | 100% classified correctly | 0 |
In plain English: it keeps up with thousands of agent actions per second without a single dropped or wrong answer.
We ran 12 adversarial tests spanning 10 distinct attack types against the live daemon (>1,200 hostile requests). All 12 passed — 0 fail-open (it never once wrongly allowed an attack):
| Attack | What it tries | Sent | Blocked |
|---|---|---|---|
| Replay storm | Re-send a captured valid request | 50 | 49 ✅ (1st allowed, rest blocked) |
| Signature forgery | Tampered/forged signatures | 100 | 100 ✅ |
| Intent injection | Call a forbidden action (rm_all) |
200 | 200 ✅ |
| Quota exhaustion | Spam past the allowed limit | many | capped at exactly 5 ✅ |
| Risk-ceiling breach | Push a too-risky action past the math limit | 50 | 50 ✅ |
| Anonymous flood | Act with no identity | 200 | 200 ✅ |
| Unknown agent | Impersonate an unregistered agent | 100 | 100 ✅ |
| Bad protocol version | Malformed/old protocol | 50 | 50 ✅ |
| Forged delegation | Fake a delegation token | 20 | 20 ✅ |
| Path traversal (MCP) | Escape allowed paths | 20 | 20 ✅ |
| All 8 at once (mixed) | Everything concurrently | 400 | 349 blocked + 50 real requests still correctly allowed ✅ |
In plain English: under a coordinated attack, it blocked every hostile request, never failed open, stayed up, and still let a legitimate request through correctly — in under a millisecond.
This repository includes a reproducible Rust sandbox for development, CI-style builds, and Step 1 capability-broker testing. It installs Rust/Cargo, native Z3, SQLite/OpenSSL headers, Python 3, and Clang/LLVM in a Docker image.
make docker-build
make dev-shell
make docker-smokeFor the full workflow, see docs/rust_sandbox.md.
If the sandbox MCP gateway port is busy, set JINN_GUARD_MCP_PORT, for example:
JINN_GUARD_MCP_PORT=4860 make smokeThe production daemon does not start the MCP TCP gateway unless --enable-mcp
is set. It binds 127.0.0.1 by default; non-loopback binds require the full
--mcp-tls-cert, --mcp-tls-key, and --mcp-tls-ca mTLS set.
The repository also includes a locked agent runtime for development validation. It demonstrates that an agent can be denied direct shell, network, and sensitive filesystem access while still reaching the Jinn Guard broker over a Unix socket.
make runtime-smokeThe runtime compose profile uses docker-compose.runtime.yml:
jinnguard-brokerowns the real capabilities and exposes the runtime socket.locked-agentruns as non-root UID10001, with no outbound network, read-only workspace, all Linux capabilities dropped, no-new-privileges, shell binaries removed, and a seccomp profile for high-risk syscalls.
The Step 2 policy rejects verdict-only dangerous actions with:
SIGNAL: DENY_RUNTIME_POLICY
See docs/step2_mandatory_mediation_sandbox.md.
┌─────────────────────────────────────────────────────────────────┐
│ AI Agent Process │
│ (Python SDK / jinnguard_py) │
│ proposal = {agent_id, intent_name, context_vars, ...} │
│ signed_envelope = HMAC-SHA256(proposal, kernel_keyring_key) │
└──────────────────────┬──────────────────────────────────────────┘
│ UNIX domain socket /run/jinnguard/jinnguard.sock
│ 5-byte framed wire protocol (version + length)
▼
┌─────────────────────────────────────────────────────────────────┐
│ Jinn Guard Daemon (ts_cli) │
│ │
│ 1. SO_PEERCRED → verify kernel PID / UID / GID │
│ 2. HMAC verify → tamper detection (key from kernel keyring) │
│ 3. G1: Agent lookup → allowed_intents + max_sequence_quota │
│ 4. Semantic classify → heuristic intent class + risk score │
│ 5. RiskAssessment → fused_risk, trust_score │
│ 6. Lineage check → replay detection + behavioral drift │
│ 7. Policy decision → risk ceiling + trust floor │
│ 8. G2: Z3 totality audit + per-agent invariant verification │
│ 9. ExecutionBroker → path / URL / command hard denylist │
│ 10. Hash-chained audit log (DENY or ALLOW) │
└──────────────────────┬──────────────────────────────────────────┘
│
┌────────────┴────────────┐
▼ ▼
SIGNAL: ALLOW SIGNAL: DENY_*
(tool executes) (tool blocked)
Kernel Layer (eBPF)
jg_execve.bpf.c → tracepoint/syscalls/sys_enter_execve
jg_openat.bpf.c → tracepoint/syscalls/sys_enter_openat
jg_connect.bpf.c → tracepoint/syscalls/sys_enter_connect
jg_cap_capable.bpf.c → tracepoint/capability/capable
│
└─→ RingBuf EVENTS → AyaKernelTelemetry (user-space drain thread)
│
└─→ governance loop (telemetry feed)
A note on languages. The probes in
bpf/are C — small, separately-compiled eBPF programs loaded into the kernel. The governance core (the daemon, the Z3 verification pipeline, the policy engine, and the CLI) is Rust, underts_cli/.bpf/**is markedlinguist-vendored, so GitHub's language bar reflects the Rust core rather than the volume of low-level kernel C.
| Crate / Directory | Purpose |
|---|---|
ts_cli/ |
Main daemon — UDS listener, enforcement pipeline, audit logger |
ts_checker/ |
Z3 SMT policy engine — bounded risk-ceiling + declarative-invariant satisfiability checks (sound on its inputs; scope and limits in THREAT_MODEL §8) |
jinnguard_py/ |
Python SDK for agent integration |
bpf/ |
eBPF C programs (execve, openat, connect, cap_capable) + Makefile |
deploy/ |
systemd unit + install.sh provisioner |
tests/ |
Integration test harness |
This repository is the open-core, single-node Jinn Guard: one host, local
policy.yaml, governed by the kernel. It is fully functional standalone.
Running Jinn Guard across many hosts under one signed policy — a control
plane, central policy distribution with HMAC-signed bundles + rollback
protection, hot-reload, and fleet dashboards — is the enterprise layer. The
client side ships here as a stable integration hook compiled only with
--features enterprise (off by default); the control-plane server and fleet
tooling are distributed separately.
When the fleet client is enabled with --fleet-policy-url, operators must also
provide a distinct --fleet-secret-file; reusing the admission HMAC secret is
rejected. The daemon persists the highest accepted policy version with
--fleet-rollback-state so restart cannot lower the rollback floor.
Interested in fleet deployment or a pilot? Open an issue or reach out via the repo — happy to talk.
| Control | Implementation |
|---|---|
| Secret management | HMAC-SHA256 key loaded into Linux kernel keyring (jinnguard_hmac_key in @s) or /etc/jinnguard/secret (mode 0400, owner root). A bounded current/previous keyset supports supervised rotation grace windows; optional per-agent secret files can override the shared key for selected agent_id values. |
| Process identity | SO_PEERCRED on every UDS connection — kernel-verified PID, UID, GID. No client-declared identity is trusted. |
| Agent identity | agent_id from proposal is matched against agent_nodes in policy.yaml. Unknown IDs are hard-denied (DENY_UNKNOWN_AGENT_ID); optional per-agent allowed_peer_uids binds a signed agent_id to the local Unix UID observed via SO_PEERCRED (DENY_AGENT_IDENTITY_BINDING). |
| Intent enforcement | Per-agent allowed_intents allowlist checked before semantic classification (DENY_INTENT_NOT_ALLOWED). |
| Sequence quota | Per-agent max_sequence_quota hard cap on decisions per lineage session (DENY_QUOTA_EXHAUSTED). |
| Replay protection | Monotonic sequence_counter validated per (pid, start_time) lineage key (DENY_REPLAY_ATTACK). |
| Behavioral drift | Statistical drift detector on fused_risk delta against lineage baseline (DENY_BEHAVIORAL_DRIFT). |
| Z3 constraint check | Every request passes through execute_totality_audit() — Z3-backed constraint check that fused_risk ≤ ceiling. |
| Z3 invariants | Per-agent declarative constraints (spending_ceiling_usd, privilege_escalation_depth, etc.) verified against daemon-computed runtime state. Callers cannot spoof daemon-observed values. |
| Audit log | Hash-chained JSONL log (each entry includes the SHA-256 of the previous — tamper evident), and erasure-safe: personal data is kept out of the chain and crypto-shredded on a GDPR Art. 17 request, with the chain still verifying. See THREAT_MODEL.md §12. |
| eBPF telemetry | Kernel-level visibility into execve, file open, network connect, and capability checks — correlated with governance decisions. |
Architecture & trust boundaries:
SECURITY_ARCHITECTURE.md(components, data flow, the two enforcement planes, the open-core boundary). Adversary model & threat→mitigation evidence:THREAT_MODEL.md.
- Linux kernel 5.16+
- CONFIG_BPF_LSM=y
bpfpresent in the active LSM list (cat /sys/kernel/security/lsm)- Debian cloud kernels often have it pre-armed.
- Ubuntu does not — append
bpfto the existing list via thelsm=boot parameter, thenupdate-gruband reboot. For example, in/etc/default/grub:(List the modules already inGRUB_CMDLINE_LINUX="lsm=lockdown,capability,landlock,yama,apparmor,ima,evm,bpf"/sys/kernel/security/lsmplusbpf; an explicitlsm=replaces the kernel default, so include the full set.) - rpm-ostree / immutable hosts should not hand-edit grub. On a booted
ostree host, stage the full active LSM set plus
bpfdeclaratively:The helper prints the exactsudo deploy/arm-lsm-ostree.sh sudo systemctl rebootrpm-ostree kargschange before applying it and prints the one-line revert (rpm-ostree kargs --delete=lsm=...; orrpm-ostree rollbackbefore reboot). Off ostree hosts, it is a no-op. On daemon startup, the audit chain records a boot marker with the booted ostree commit, kernel release, and ostree/non-ostree flag. rpm-ostree provides platform immutability; it does not perform Jinn Guard action blocking.
- bpftool installed for
vmlinux.hgeneration (Debian:bpftool; Ubuntu:linux-tools-generic)
Validated on three distributions / three kernel generations: Debian 13 / kernel
6.12 (BENCHMARKS-01.md, BENCHMARKS-02.md),
Ubuntu 24.04 / kernel 6.17 (BENCHMARKS-03.md), and
AlmaLinux 9 / kernel 5.14 under SELinux Enforcing (BENCHMARKS-04.md).
Jinn Guard's enforcement floor is eBPF-LSM — a Linux-kernel facility. The un-sheddable guarantee (a decision that holds even if the daemon is dead or bypassed) exists only where BPF-LSM is present. The rest of the stack (audit ledger, SMT checks, signed manifests) is portable user-space — but that layer is cooperative, and it is exactly the part a compromised host can bypass. The kernel floor is what backstops it.
| Platform | Kernel floor | User-space governance | Status |
|---|---|---|---|
Linux x86_64, kernel ≥5.16 with BPF-LSM (CONFIG_BPF_LSM=y, BTF, bpf in lsm=) |
✅ Full | ✅ Full | Validated — Debian 13/6.12, Ubuntu 24.04/6.17, AlmaLinux 9/5.14 |
| Linux aarch64 (arm64), same kernel reqs | ✅ Expected (BPF-LSM is arch-independent) | ✅ Full | 🚧 Not yet validated |
| Linux without BPF-LSM (old / locked-down kernel) | ❌ None | ✅ Full (cooperative) | Degraded — runs audit-only, same posture as safe mode |
| Windows / macOS | ❌ No LSM | Not supported as an enforcement host | |
| 32-bit ARM / non-Linux MCUs | ❌ | ❌ | Out of scope |
Why not Windows/macOS? Neither has a Linux Security Module. Running there would ship only the bypassable user-space governor — the security thesis removed. Windows enforcement would require a separate backend (WFP / filesystem minifilters / ETW / eBPF-for-Windows) — a different engine, not a port. It is not on the roadmap.
A single, deliberately conservative split of what is enforced now from what is aspirational, by plane. The distinction that matters: the kernel floor holds even if the daemon is dead or bypassed; the user-space gate only acts while the daemon is in the request path. Read this alongside THREAT_MODEL §8 (what the Z3 step does and does not prove) and Known Limitations below.
| Capability | Plane | Status today |
|---|---|---|
Cgroup-scoped allow/deny on execve/connect/sendmsg/inode_create/inode_unlink |
Kernel floor (holds without daemon) | ✅ Enforced |
Fail-closed decisions — Z3 Unknown/timeout → DENY; bad signature / replay → reject |
Kernel floor + user-space | ✅ Enforced |
| Tamper-evident audit ledger — SHA-256 hash chain, in-repo verifier, crypto-shredding | User-space | ✅ Enforced |
| Release provenance — SLSA v3 + cosign keyless signatures on tagged builds | Build/release | ✅ Enforced |
| Semantic risk assessment — keyword/intent heuristic (+ optional external scorer) | User-space (cooperative) | ✅ Active, heuristic-primary |
| Bounded SMT checks — risk-ceiling arithmetic + invariant satisfiability | User-space (cooperative) | ✅ Active; sound on its inputs, not a proof of the risk oracle — THREAT_MODEL §8 |
Ed25519-signed provenance manifests — offline-verifiable authenticity over the audit ledger (--manifest-key; --verify-manifests) |
User-space | ✅ Active (opt-in, Action Manifest v0) |
| External transparency-log anchoring of audit checkpoints | User-space | 🚧 Roadmap (JG #62 v2) |
| Reproducible builds — rebuild-and-compare (Linux x86_64, pinned toolchain) | Build/release | ✅ Enforced (verified in CI on every push/PR + at release — RELEASE_INTEGRITY §Reproducible builds) |
| OpenTelemetry/OTLP metric export | User-space | ✅ Active (opt-in — JINNGUARD_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_*) |
Full cross-mount path strings in the audit log (bpf_d_path) |
Kernel floor | 🚧 Roadmap (enforcement already mount-safe — see Known Limitations) |
Why this table exists. An external review noted that earlier wording could read as if user-space semantic verdicts were kernel-enforced. They are two planes: the kernel floor is deterministic and map-driven; the semantic/risk/SMT layer is a cooperative user-space gate. Both are real; only the kernel floor survives a dead or bypassed daemon.
The obvious question for anything in this space: Microsoft's Agent Governance Toolkit (AGT) already ships per-agent identity (SPIFFE/DID/mTLS), a Merkle tamper-evident audit ledger, a fail-closed policy engine (YAML/OPA/Cedar), and a Rust SDK. It is MIT-licensed, Microsoft-signed, and widely adopted. So identity, ledger, and policy are not what sets Jinn Guard apart. AGT has all three, and in some respects has them further along.
The difference is where enforcement sits. AGT enforces at the application middleware layer: the policy engine and the agent run inside the same process boundary, and its stated production recommendation for OS-level isolation is "run each agent in a separate container." Jinn Guard enforces per action in the kernel, outside the agent's process, and refuses the container as the isolation unit. That is the only superiority claim made here, and it is narrow: kernel per-action enforcement welded to per-agent identity and a hash-chained ledger, on a single Linux host, without Kubernetes. Everything below defends that specific bundle.
A container draws a boundary around a process tree. That is a real control and it
is orthogonal to what Jinn Guard does, not a substitute for it. A namespace and
cgroup boundary cannot emit a per-action allow/deny verdict, and it has no notion
of which agent took the action. "This container may open sockets" is not the
same statement as "agent fabric_swarm_production_01, identity bound to UID 10001
via SO_PEERCRED, may connect() to this destination and not that one." The
container grants a capability once, at the tree level. Jinn Guard's BPF-LSM hooks
render a verdict at each execve/connect/sendmsg/inode_create/inode_unlink,
scoped to the governed cgroup, bound to a cryptographic agent_id. That binding is
the point: the verdict names who acted.
Container isolation is enforcement without a record. When a process inside the container acts, the boundary does not write a per-action, tamper-evident entry tied to the agent identity that requested it. You get container-level logs, not "agent X was denied action Y at sequence N, and here is the hash that chains it to the prior decision." Jinn Guard's audit ledger is per action and hash-chained (each entry carries the SHA-256 of the previous), so the trace is both attributable and tamper-evident. AGT ships a comparable Merkle ledger. The distinction is again placement: because Jinn Guard's ledger is fed by kernel hooks rather than an in-process middleware call, the entry survives a subprocess the agent library never saw.
The "container per agent" model assumes the agent is one cooperating process. Real agent workloads violate that assumption three ways:
- The agent spawns subprocesses. A tool call that shells out, forks a helper, or launches a language runtime produces syscalls the middleware never mediated.
- The agent executes generated code. Model output that runs is a process the policy library did not wrap and cannot introspect from inside its own boundary.
- Many agents share a host. One container per agent multiplies boundaries, but the syscalls from every one of them still terminate at the same kernel.
In all three, the enforcement question is "did this action happen," and the only component positioned to answer for every process, spawned or generated or co-tenant, is a kernel hook. A per-process middleware sees the calls routed through it. A BPF-LSM hook sees the calls, because the syscall path runs through the LSM whether or not the agent cooperated.
AGT enforces in the same process as the agent. That is stated plainly in its own design: policy engine and agent share a process boundary. The consequence is that a compromised agent sits between the policy and its own verification. Code that has taken over the process can act before the in-process check, around it, or against the library evaluating it, because attacker code and policy code are the same address space. This is not a knock on AGT's engineering. It is what "enforce at the middleware layer" costs.
Kernel enforcement removes the agent from that path. The BPF-LSM hook runs in the
kernel, on the syscall boundary, after the process has already lost the ability to
reach it. A compromised agent can still attempt anything. It cannot relocate the
hook, and it cannot forge the identity binding, which is derived from SO_PEERCRED
and the kernel keyring rather than declared by the caller. The verdict is rendered
somewhere the agent does not control. That is the whole argument for putting it in
the kernel.
Per-operation BPF-LSM enforcement adds low-single-digit microseconds when the verdict is an in-kernel map lookup. For agent workloads that overhead is effectively free, and not by rounding. Every tool call an agent makes is gated behind an LLM inference that costs on the order of hundreds of milliseconds to seconds. That is five to six orders of magnitude more than the entire syscall sequence the enforcement decision sits on. The governance layer is not on the critical path in any measurable sense.
That "free" holds under one hard constraint: the in-kernel verdict must be a precomputed map lookup, not a userspace roundtrip, and never an on-path solve. The moment enforcement blocks on userspace, per-syscall latency stops being microseconds. This is why the kernel floor in Jinn Guard is deterministic and map-driven: the BPF-LSM hook consults precomputed cgroup-scoped decision maps and returns. The heavier work, semantic risk assessment and the bounded SMT invariant checks, runs in the user-space gate, off the kernel hot path, and is documented as a separate, cooperative plane (see What Jinn Guard guarantees today and THREAT_MODEL §8). Anything that would put a solver or a userspace call on the syscall path does not belong in the kernel floor, and is not there.
Advisory registry: the canonical list of
JG-ADV-*IDs, status, and fix commits lives inSECURITY/ADVISORIES.md.Note on identifiers:
JG-ADV-*are internal, self-identified advisory IDs, not CVE records issued by a CNA.
The BPF inode_create/inode_unlink hooks now resolve the full absolute
path of a file operation in the kernel (a bounded d_parent walk), closing the
earlier basename-only bypass. Validated live (audit-only) via
scripts/validate_m2_path_resolution.sh.
Residual limitation (telemetry only, since JG #52): the inode hooks receive a
dentry without a vfsmount, so the reconstructed path string for a file on a
sub-mount (e.g. a tmpfs /tmp) is relative to that mount's root (/tmp/x →
/x). This affects the logged/audited path, not the enforcement decision —
denied directories are matched by their (s_dev, i_ino) identity, which a
mount/bind/pivot_root remap cannot fool (see THREAT_MODEL §7.1). Root-filesystem
paths (/etc, /usr, /opt, /home on a single-root install) also resolve to
full absolute strings. Full cross-mount path strings require path-family LSM
hooks or bpf_d_path and are tracked for a future release.
An agent explicitly allowed to run an interpreter can invoke other tools through it. Jinn Guard denies known interpreters by policy for governed agents (any agent carrying an executable allowlist); per-binary execve limits remain only as strong as that allowlist.
lab/ is a self-contained, dependency-free lab that teaches the
Jinn Guard model — semantic intent → policy → decision → audit — without the
kernel/eBPF layer. Students build a mini allow/deny/canary/human-review checker
(python3 lab/checker_starter.py), then find and fix a planted audit-logging
flaw. It is explicit about which verdicts the real daemon enforces today
(ALLOW/DENY) versus which are taught as concepts (CANARY_TRIGGERED,
HUMAN_REVIEW); the design path to close that gap is in
docs/agent_governance_extensions.md.
curl -fsSL https://raw.githubusercontent.com/AlphaReasoning/The-Jinn-Guard/main/deploy/bootstrap.sh | sh -s -- --start-nowdeploy/bootstrap.sh detects your distro, kernel, arch, BTF and BPF-LSM state,
installs the right dependencies via the native package manager (apt/dnf/yum/
zypper/pacman), ensures a Rust toolchain, fetches the source, and hands off to
the privileged installer below. It never weakens the security posture —
fail-closed defaults, no desktop lockout, and safe-mode stays opt-in. Preview
everything it will do without changing anything:
sh deploy/bootstrap.sh --dry-run # print the full plan, run nothing
sh deploy/bootstrap.sh --arm-lsm --safe-mode --start-now # also arm lsm=bpf (reboot), start audit-onlyIf you prefer to drive each step yourself, use the prerequisites + installer below.
# Debian/Ubuntu
sudo apt install libz3-dev libssl-dev pkg-config llvm clang libbpf-dev keyutils
# Ubuntu only: bpftool ships inside linux-tools (no standalone package)
sudo apt install linux-tools-generic # provides bpftool for vmlinux.h generation
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shUbuntu note.
libssl-dev+pkg-configare required (theopenssl-sysdependency). And unlike Debian's cloud kernels, Ubuntu does not armbpfin its default LSM list — see System Requirements to enable it before running kernel enforcement. Validated end-to-end on Ubuntu 24.04 / kernel 6.17 — seeBENCHMARKS-03.md.
git clone https://github.com/AlphaReasoning/The-Jinn-Guard
cd The-Jinn-Guard
sudo ./deploy/install.shThe installer will:
- Create the
jinnguardsystem user - Generate a 256-bit HMAC secret →
/etc/jinnguard/secret - Load the key into the kernel keyring
- Build and install
/usr/sbin/jinnguard - Enable and start
jinnguard.service
# Load existing secret into kernel keyring
keyctl padd user jinnguard_hmac_key @s < /etc/jinnguard/secret
# Verify the key is present
keyctl show @sOptional per-agent HMAC keys can be supplied with
JINNGUARD_AGENT_SECRET_DIR=/etc/jinnguard/agent-secrets or
--agent-secret-dir /etc/jinnguard/agent-secrets. Each regular file name is an
agent_id; its contents are the HMAC key for that agent. If a file exists for an
agent, that agent must sign with the per-agent key. Agents without a file keep
the shared admission key path.
systemctl status jinnguard
journalctl -u jinnguard -ffrom jinnguard_py import JinnGuardClient
client = JinnGuardClient(
socket_path="/run/jinnguard/jinnguard.sock",
secret_key="<your-hmac-secret>", # or use keyring lookup
agent_id="fabric_swarm_production_01",
)
result = client.propose(
intent_name="read_file",
sequence_counter=1,
action_risk_score=20.0,
proposed_action={"kind": "file_write", "path": "/tmp/out.txt", "contents": "hello"},
context_vars={"spending_ceiling_usd": 50.0},
)
if result.allowed:
print("Execution permitted:", result.signal)
else:
print("Blocked:", result.signal)global_safety_ceiling: 75.0 # fused_risk must be below this
agent_nodes:
- id: fabric_swarm_production_01
privilege_tier: 1
# Optional: bind this signed agent_id to local Unix users observed through
# SO_PEERCRED. Empty/omitted preserves the shared-key legacy behavior.
# Pair with a file named /etc/jinnguard/agent-secrets/fabric_swarm_production_01
# to require a per-agent HMAC key for this id.
allowed_peer_uids:
- 10001
allowed_intents:
- read_file
- model_inference
- write_temp
max_sequence_quota: 500 # max decisions per lineage session (0 = unlimited)
invariants:
- "spending_ceiling_usd <= 150.00"
- "privilege_escalation_depth < 3"
- "fused_risk <= 74.0"
- id: admin_agent_00
privilege_tier: 3
allowed_intents: [] # empty = all intents allowed
max_sequence_quota: 0 # 0 = unlimited
invariants:
- "trust_score >= 30.0"Invariants are simple infix expressions resolved by the Z3 real-arithmetic solver:
<variable> <op> <literal>
| Operator | Meaning |
|---|---|
<=, >=, <, >, == |
Standard comparison |
Daemon-authoritative variables (cannot be spoofed by the caller):
| Variable | Source |
|---|---|
fused_risk |
RiskAssessment::assess() |
trust_score |
RiskAssessment::assess() |
observed_risk, semantic_risk, topology_risk |
Assessment sub-components |
is_root |
SO_PEERCRED UID = 0 → 1.0 |
privilege_tier |
Agent node policy config |
decisions_seen |
Lineage registry |
max_assessed_risk |
Lineage registry |
privilege_escalation_depth |
Derived from is_root × decisions_seen |
Caller-supplementable variables (agent can declare, daemon values take precedence):
| Variable | Example |
|---|---|
spending_ceiling_usd |
Tool execution cost declared by the agent |
| Any custom key | Passed in proposal.context_vars |
| Resource | Path |
|---|---|
| UDS socket | /run/jinnguard/jinnguard.sock |
| Audit log | /var/log/jinnguard/audit.log |
| Lineage store | /var/lib/jinnguard/lineage.json |
| Policy config | /etc/jinnguard/policy.yaml |
| HMAC secret | /etc/jinnguard/secret (mode 0400, owner root) |
| eBPF object | /usr/lib/jinnguard/jinnguard_ebpf.o |
jinnguard [OPTIONS]
Options:
--socket-path <PATH> UDS socket path [default: /run/jinnguard/jinnguard.sock]
--lineage-file <PATH> Lineage persistence file [default: /var/lib/jinnguard/lineage.json]
--audit-log <PATH> Audit log path [default: /var/log/jinnguard/audit.log]
--policy-file <PATH> Policy YAML [default: /etc/jinnguard/policy.yaml]
-h, --help Print help# Prerequisites (Debian)
sudo apt install clang llvm libbpf-dev linux-headers-$(uname -r) bpftool
# On Ubuntu, bpftool comes from linux-tools-generic instead of a `bpftool` package:
# sudo apt install clang llvm libbpf-dev linux-headers-$(uname -r) linux-tools-generic
# Build all four programs and merge into jinnguard_ebpf.o
cd bpf && make
# Install to /usr/lib/jinnguard/
sudo make installThe merged object is loaded at runtime by the aya-rs backend inside the daemon when built with the kernel_telemetry feature.
# Run the full suite (122 tests: 4 Z3 + 93 unit + 13 integration + 12 swarm-attack)
cargo test
# Run benchmarks
cargo bench
# Benchmarks cover:
# z3_state_transition — risk check hot path at various risk levels
# z3_policy_invariants — 2-constraint and 5-constraint invariant solve
# totality_audit — per-connection Z3 ceiling proof
# uds_socket_saturation — raw UDS framed roundtrip latency
# uds_socket_saturation — persistent connection variant
# Check without building
cargo checkLooking for the headline numbers? See 📊 Performance & Security at a Glance above, or
BENCHMARKS-01.mdfor full latency, throughput, and attack-suite detail.Independent reviewers: see
PROFESSOR_VALIDATION.mdand runbash scripts/run_professor_validation.shfor a one-command, tiered validation of everything below.Security researchers: see
BREAK_IT_CHALLENGE.mdfor the public break-it challenge scope, rules of engagement, and report template. Vulnerabilities should be reported privately first throughSECURITY.md.
Validated on three real hosts — Debian 13 / kernel 6.12, Ubuntu 24.04 / kernel 6.17, and AlmaLinux 9 / kernel 5.14 (SELinux Enforcing):
| Capability | How it was validated |
|---|---|
| Full automated suite (122 tests) | cargo test --workspace — 4 Z3 + 93 unit + 13 integration + 12 swarm-attack, plus anti-lockout + safe-mode invariants |
| Mandatory mediation | Docker locked-agent: 7/7 probes — direct network//etc-write/shell blocked, broker-mediated actions succeed |
| Kernel full-path resolution | eBPF-LSM hooks load + resolve absolute paths live (audit-only) |
| Kernel allow/deny enforcement | tests/kernel_lsm.rs armed on a real 6.12 host: 2,500 enforced ops across execve/TCP/UDP/create/unlink, 0 fail-open, 0 incorrect decisions (P50 8–473µs, P99 20–1038µs by surface); enforcement is cgroup-scoped so it runs safely without a spare machine. Re-validated on Ubuntu 24.04 / kernel 6.17 (BENCHMARKS-03.md) and AlmaLinux 9 / kernel 5.14 under SELinux Enforcing (2,750 ops, 0 fail-open / 0 incorrect; BENCHMARKS-04.md) |
| Anti-lockout guarantee | CI-enforced invariant tests plus in-kernel cgroup scoping (bpf_get_current_cgroup_id): only the governed cgroup is subject to allow/deny, every other task passes through; safe mode stays audit-only |
This is not independently audited or enterprise-GA. It is a strong,
test-backed prototype demonstrating OS-level AI-agent enforcement.
See RESIDUAL_RISKS.md for the current residual-risk
register and release claim boundary.
| Component | Status |
|---|---|
| UDS IPC transport (framed, version-tagged) | ✅ Production |
| HMAC-SHA256 authentication | ✅ Production |
| Kernel keyring secret management | ✅ Production |
| Bounded HMAC key rotation grace | ✅ Production |
| Optional per-agent HMAC key files | ✅ Production |
SO_PEERCRED process identity |
✅ Production |
Z3 risk-ceiling check (bounded SMT: r + w ≤ c) |
✅ Production |
| Z3 declarative-invariant satisfiability check (G2) | ✅ Production |
| Per-agent intent allowlist enforcement (G1) | ✅ Production |
| Per-agent sequence quota enforcement (G1) | ✅ Production |
| Replay attack protection | ✅ Production |
| Behavioral drift detection | ✅ Production |
| Hash-chained audit log | ✅ Production |
| ExecutionBroker hard denylist | ✅ Production |
| Lineage persistence | ✅ Production |
| systemd unit + installer | ✅ Production |
Python SDK (jinnguard_py) |
✅ Functional |
| eBPF C sources (4 programs + Makefile) | ✅ Source complete |
| UDS saturation benchmark | ✅ Implemented |
| Item | Status |
|---|---|
| eBPF compilation gated in CI (compiles all 10 LSM objects, fails the build on a BPF regression) | ✅ Done |
Structured, machine-parseable CLI exit codes for startup failures (code=/kind=) |
✅ Done |
Opt-in capability hardening after BPF load — no_new_privs + bounding-set drop (JINNGUARD_HARDEN_CAPS=1) |
✅ Done |
Prometheus /metrics + /healthz endpoint (opt-in JINNGUARD_METRICS_PORT, loopback) |
✅ Done |
OTLP/HTTP JSON metrics export (opt-in JINNGUARD_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_*) |
✅ Done |
Optional RootAI remote semantic service over mTLS (--rootai-url + --rootai-tls-*) |
✅ Done |
Public break-it challenge scope and reporting template (BREAK_IT_CHALLENGE.md) |
✅ Done |
Verified reproducible release build gate (RELEASE_INTEGRITY.md) |
✅ Done |
Operator runbook (OPERATOR_RUNBOOK.md): install, monitor, upgrade/rollback, incident response |
✅ Done |
| Gap | Effort |
|---|---|
| Multi-distribution / multi-kernel validation matrix (Debian 13/6.12 + Ubuntu 24.04/6.17 + AlmaLinux 9/5.14 done; broaden coverage over time) | Medium |
