Autonomy, Contained.
LLM proposes commands. Chainwatch enforces policy. No commands are hardcoded.
Chainwatch audit log: every decision recorded, hash chain verified.
Runtime control plane for AI agent safety. Intercepts tool calls at irreversible boundaries — payments, credentials, data destruction, external communication — and enforces deterministic policy decisions.
A single Go binary that wraps agent tool invocations, evaluates deterministic policy, and enforces decisions (allow, deny, require-approval) at boundaries agents cannot bypass.
- Not an ML-based anomaly detector
- Not a logging/observability layer (enforcement, not detection)
- Not an LLM guardrail for prompt content
- Not a permissions system (enforces boundaries, not roles)
- Not a web UI or SaaS product (CLI only)
In February 2026, a supply chain attack on Cline compromised ~4,000 developer machines. The attack chain:
- Attacker injects a prompt into a GitHub issue title
- An AI triage bot reads the issue, interprets it as an instruction, and executes
npm installfrom an attacker-controlled repository - A preinstall script exfiltrates the project's npm publish token
- The attacker publishes a compromised package that installs malware on end-user machines
The root cause: the AI agent had unrestricted tool execution in the same context as untrusted input and production credentials. No structural boundary existed between reading an issue title and running npm install with secret-bearing environment variables.
Chainwatch prevents this class of attack by enforcing policy at the execution boundary:
- Denylist blocks
npm publish,npm installfrom untrusted sources, credential access patterns, and egress to unknown hosts - Risk scoring classifies credential + egress combinations as IRREVERSIBLE — hard deny, no approval workflow
- Profiles restrict what each agent identity can do: a triage bot gets read-only access, never
exec - Audit log records every decision with hash chain integrity, so compromised actions are traceable
The enforcement is structural, not cooperative. The agent does not choose whether to consult the policy — every tool call routes through chainwatch before execution.
curl -fsSL https://raw.githubusercontent.com/ppiankov/chainwatch/main/scripts/install.sh | bashDownloads the binary, runs chainwatch init, and verifies with chainwatch doctor.
go install github.com/ppiankov/chainwatch/cmd/chainwatch@latest
chainwatch init# Linux (amd64)
curl -sL https://github.com/ppiankov/chainwatch/releases/latest/download/chainwatch-linux-amd64 -o chainwatch
chmod +x chainwatch && sudo mv chainwatch /usr/local/bin/
# macOS (Apple Silicon)
curl -sL https://github.com/ppiankov/chainwatch/releases/latest/download/chainwatch-darwin-arm64 -o chainwatch
chmod +x chainwatch && sudo mv chainwatch /usr/local/bin/Then bootstrap:
chainwatch init
chainwatch doctorgit clone https://github.com/ppiankov/chainwatch.git
cd chainwatch
go build -o chainwatch ./cmd/chainwatchchainwatch is designed to be used by autonomous agents without plugins or SDKs. Single binary, deterministic policy enforcement, structured JSON output, bounded jobs.
Agents: read SKILL.md for commands, flags, JSON output patterns, and approval workflow.
Key pattern for agents: chainwatch exec --dry-run --profile <p> -- <cmd> to check policy before executing.
# Safe command — allowed
chainwatch exec -- echo "hello world"
# Dangerous command — blocked
chainwatch exec -- rm -rf /
# Decision: deny | Reason: Denylisted: command matches pattern: rm -rf# Enforce a safety profile
chainwatch exec --profile coding-agent -- curl https://api.example.com/data
# Available profiles: coding-agent, research-agent, customer-support, data-analyst, clawbot# One command — creates policy.yaml, denylist.yaml, profiles/
chainwatch init
# With a built-in profile
chainwatch init --profile clawbot
# Verify setup
chainwatch doctorAgent ──► chainwatch ──► Tool
│
┌────┴────┐
│ Policy │ Deterministic rules, thresholds, denylist
│ Engine │ YAML-configurable, hot-reloadable
└────┬────┘
│
allow / deny / require-approval
| Mode | Command | Use Case |
|---|---|---|
| CLI wrapper | chainwatch exec -- <cmd> |
Single agent, simplest setup |
| gRPC server | chainwatch serve |
Multi-agent, SDK integration |
| HTTP proxy | chainwatch proxy |
Intercept agent HTTP traffic |
| LLM intercept | chainwatch intercept |
Extract tool calls from streaming LLM responses |
| MCP server | chainwatch mcp |
Claude Desktop integration |
Enforcement: exec, serve, proxy, intercept, mcp, evaluate
Approval workflow: approve, deny, pending
Emergency override: breakglass create, breakglass consume, breakglass revoke, breakglass list
Audit: audit verify
Policy tools: policy diff, policy simulate, policy gate, certify
Setup: init, doctor, recommend, init-denylist, init-policy, generate-apparmor, generate-selinux, version
Chainwatch uses deterministic YAML-based policy. No ML or statistical models.
# ~/.chainwatch/denylist.yaml
commands:
- "rm -rf"
- "sudo su"
- "curl * | sh"
urls:
- "evil.com"
- "*.onion"
files:
- "~/.ssh/id_rsa"
- "~/.aws/credentials"# ~/.chainwatch/policy.yaml
enforcement_mode: guarded
thresholds:
allow_max: 5
approval_min: 11
rules:
- purpose: "SOC_efficiency"
resource_pattern: "*salary*"
decision: deny
reason: "salary data blocked for SOC purpose"
- purpose: "*"
resource_pattern: "*credentials*"
decision: require_approval
reason: "credential access requires approval"
approval_key: cred_access# Agent hits require_approval → operator approves
chainwatch pending # List pending approvals
chainwatch approve salary_access --ttl 5m # Approve with TTL
chainwatch deny salary_access # Deny
# Emergency override
chainwatch breakglass create --reason "incident response"
chainwatch exec --breakglass <token> -- <cmd>import "github.com/ppiankov/chainwatch/sdk/go/chainwatch"
client, _ := chainwatch.New("localhost:9090")
defer client.Close()
result, _ := client.Evaluate(&model.Action{
Tool: "command",
Resource: "rm -rf /tmp/data",
Operation: "execute",
}, "general", "")
if result.Decision == model.Deny {
fmt.Println("Blocked:", result.Reason)
}from chainwatch_sdk import ChainwatchSDK
cw = ChainwatchSDK()
result = cw.evaluate(tool="command", resource="rm -rf /", operation="execute")
if result["decision"] == "deny":
print(f"Blocked: {result['reason']}")make go-test # Run Go tests with -race
make test # Run Python tests
make fieldtest # Run adversarial test suite (5 rounds)
make bench # Run benchmarks
make fuzz # Run fuzz tests (30s per target)Principiis obsta — resist the beginnings.
- Deterministic policy, not ML predictions
- Enforcement at irreversible boundaries, not after
- The system NEVER asks the model whether an irreversible action is safe
- Monotonic risk: SAFE -> SENSITIVE -> COMMITMENT -> IRREVERSIBLE (one-way)
- Fail-closed: unreachable server = deny
See docs/irreversible-boundaries.md and docs/DESIGN_BASELINE.md.
- CLI-only — no web UI or dashboard
- Single-node — no distributed coordination or multi-tenant support
- Process wrapping — runtime enforcement is process-level (
exec); seccomp can be generated for container hardening viachainwatch profile seccomp - Python SDK — subprocess-based (wraps the Go binary, not a native library)
- No content inspection — classification based on resource names, not file contents
Business Source License 1.1 — source available, free for non-production use. Production use requires a commercial license. Converts to Apache 2.0 after 4 years per version.