Skip to content

Latest commit

 

History

259 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🛡️ Jinn Guard — Kernel-aware agent governance daemon (validated research prototype)

CI

🛡️ 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.

▶️ Watch it run

Jinn Guard live demo — kernel-aware agent governance daemon blocking AI-agent attacks

Automated demo (Rust + eBPF): one legitimate request is allowed; real attacks are blocked live by the daemon.

▶️ See it live in 5 minutes

bash scripts/demo.sh        # or: bash scripts/demo.sh --auto

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

🔬 For evaluators — don't trust it, verify it

python3 scripts/validate/validate.py        # attacks, determinism, audit-chain, tamper-proof

A 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/ and scripts/validate/CLAIMS.md.


📊 Performance & Security at a Glance (measured)

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 resistance

Full detail: BENCHMARKS-01.md.

⚡ How fast is each decision?

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.

🚀 How much can it handle?

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.

🛡️ Can it be attacked or tricked?

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.


Rust Sandbox / Dev Environment

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-smoke

For 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 smoke

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

Step 2 Mandatory Mediation Runtime

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-smoke

The runtime compose profile uses docker-compose.runtime.yml:

  • jinnguard-broker owns the real capabilities and exposes the runtime socket.
  • locked-agent runs as non-root UID 10001, 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.

🏗️ Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         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, under ts_cli/. bpf/** is marked linguist-vendored, so GitHub's language bar reflects the Rust core rather than the volume of low-level kernel C.


📦 Components

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

🏢 Fleet & Enterprise

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.


🔐 Security Model

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.


System Requirements

  • Linux kernel 5.16+
  • CONFIG_BPF_LSM=y
  • bpf present in the active LSM list (cat /sys/kernel/security/lsm)
    • Debian cloud kernels often have it pre-armed.
    • Ubuntu does not — append bpf to the existing list via the lsm= boot parameter, then update-grub and reboot. For example, in /etc/default/grub:
      GRUB_CMDLINE_LINUX="lsm=lockdown,capability,landlock,yama,apparmor,ima,evm,bpf"
      
      (List the modules already in /sys/kernel/security/lsm plus bpf; an explicit lsm= 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 bpf declaratively:
      sudo deploy/arm-lsm-ostree.sh
      sudo systemctl reboot
      
      The helper prints the exact rpm-ostree kargs change before applying it and prints the one-line revert (rpm-ostree kargs --delete=lsm=...; or rpm-ostree rollback before 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.h generation (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).


Platform support

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 ⚠️ User-space layer only 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.


What Jinn Guard guarantees today — vs 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 oracleTHREAT_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.


Why kernel enforcement, not a container per agent

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 isolates a process tree, not an action

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.

A container leaves no per-action, attributable trace

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.

One container per agent breaks at the seams

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.

The shared process boundary is the actual gap

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.

Performance: the verdict must be an in-kernel lookup

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.


Known Limitations

Advisory registry: the canonical list of JG-ADV-* IDs, status, and fix commits lives in SECURITY/ADVISORIES.md.

Note on identifiers: JG-ADV-* are internal, self-identified advisory IDs, not CVE records issued by a CNA.

Filesystem path resolution — mount boundaries (was JG-ADV-2026-002, now fixed)

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.

Interpreter chains (JG-ADV-2026-001, mitigated)

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.


🎓 Teaching lab — learn the governance model

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.

🚀 Quick Start

One command (bare host → running node)

curl -fsSL https://raw.githubusercontent.com/AlphaReasoning/The-Jinn-Guard/main/deploy/bootstrap.sh | sh -s -- --start-now

deploy/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-only

If you prefer to drive each step yourself, use the prerequisites + installer below.

Prerequisites

# 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 | sh

Ubuntu note. libssl-dev + pkg-config are required (the openssl-sys dependency). And unlike Debian's cloud kernels, Ubuntu does not arm bpf in 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 — see BENCHMARKS-03.md.

Install (automated)

git clone https://github.com/AlphaReasoning/The-Jinn-Guard
cd The-Jinn-Guard
sudo ./deploy/install.sh

The installer will:

  1. Create the jinnguard system user
  2. Generate a 256-bit HMAC secret → /etc/jinnguard/secret
  3. Load the key into the kernel keyring
  4. Build and install /usr/sbin/jinnguard
  5. Enable and start jinnguard.service

Manual key provisioning

# Load existing secret into kernel keyring
keyctl padd user jinnguard_hmac_key @s < /etc/jinnguard/secret

# Verify the key is present
keyctl show @s

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

Service management

systemctl status jinnguard
journalctl -u jinnguard -f

Python client example

from 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)

⚙️ Configuration — policy.yaml

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"

Invariant syntax

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

🗂️ Runtime Paths

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

🔧 CLI Flags

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

🧬 Building eBPF Programs

# 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 install

The merged object is loaded at runtime by the aya-rs backend inside the daemon when built with the kernel_telemetry feature.


🧪 Development

# 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 check

📊 Validation status — validated research prototype / controlled-pilot MVP

Looking for the headline numbers? See 📊 Performance & Security at a Glance above, or BENCHMARKS-01.md for full latency, throughput, and attack-suite detail.

Independent reviewers: see PROFESSOR_VALIDATION.md and run bash scripts/run_professor_validation.sh for a one-command, tiered validation of everything below.

Security researchers: see BREAK_IT_CHALLENGE.md for the public break-it challenge scope, rules of engagement, and report template. Vulnerabilities should be reported privately first through SECURITY.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.

✅ Shippable components

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

✅ Recently closed (post-rc1 hardening)

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

🔴 Remaining gaps before 100%

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

About

Kernel-aware agent governance daemon. Tamper-evident hash-chained audit ledger, BPF-LSM enforcement, Z3-backed policy checks. Research prototype.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages