@tuent/sentinel
v0.1.7
Published
AI agent behavioral security monitoring SDK
Downloads
905
Readme
@tuent/sentinel
Policy enforcement, behavioral detection, and a signed audit trail for Claude Code. Sentinel enforces your policy on every Claude Code tool call before it runs, watches for behavior that deviates from the agent's norm, and records every decision to a per-project, cryptographically signed trail you can verify end to end.
For teams adopting Claude Code that need enforceable guardrails on what the agent can do, an eye on how it's behaving, and a tamper-evident record they can hand to a reviewer.
What you get
Prevent — policy enforced before execution. Every Claude Code tool call is checked against your .sentinel.yaml and allowed or denied before it runs, not flagged afterward. Out of the box the policy protects credential stores, private keys, cloud configs, and system directories; a network allowlist gates tool-level requests; and repeated violations escalate the agent normal → restricted → quarantined at thresholds you set — one command restores it.
Detect — a behavioral baseline per agent. Sentinel learns each agent's normal pattern of work and surfaces deviations from it in its reports — an anomaly layer on top of the static rules, for the novel behavior a fixed list won't catch. It informs; it doesn't block.
Prove — a signed, verifiable record of every tool call. Every decision — allowed or denied — is appended to a per-project, hash-chained, Ed25519-signed trail anchored by a signed manifest. sentinel --verify-audit re-walks the whole chain and checks every signature, so you can prove after the fact which tool calls ran and which Sentinel stopped. This is the strongest guarantee Sentinel makes: even an action Sentinel can't prevent, it can detect and prove.
Quick start
npm install @tuent/sentinel
npx sentinel init claude-codeinit writes a starter .sentinel.yaml to your project, installs the Claude Code hook, and merges a hook entry into your project settings. The gateway daemon auto-starts the next time Claude Code runs — no build step, no separate service to manage. Requires Node.js ≥ 20.
Working with a specific stack? npx sentinel init claude-code --workload=google-workspace (or microsoft-365) starts you from a policy whose network allowlist already includes that stack's API hosts. Workloads differ only in the network allowlist — the file and action rules are identical — so it's a convenient starting policy, not a separate security mode.
In practice
- A blocked secret read, recorded. The agent tries to read a private key; Sentinel denies it before the read and writes a signed
unauthorized_targetdecision. The agent keeps working — and you have proof it was stopped. - A network request held to your allowlist. A tool-level fetch to a host you didn't list is denied, with a message naming
allow.networkHostsand how to add it. Add the host and it's allowed — that allow is in the trail too. - A deviation surfaced. Once an agent has an established baseline,
sentinel --reportflags when its current behavior diverges from that norm — a prompt to look, not an automatic block. - Verify the record end to end.
sentinel --verify-audit --agent=<id>walks the hash chain and checks every Ed25519 signature, reporting each decision as allowed or denied. Hand the output to whoever needs to trust the run.
Policy
.sentinel.yaml is the single policy file init writes. The parts you'll tune:
policy:
allow:
actions: [file_read, file_write, tool_invocation, network_request, command_exec]
targets: # advisory for file/tool actions — logged if outside, not blocked
- "src/**"
- "test/**"
- "**/*.md"
networkHosts: # a tool-level network_request is DENIED unless the host is listed
- "api.anthropic.com"
- "github.com"
- "registry.npmjs.org"
forbid:
targets: # hard deny; the generated policy ships a default floor for credential
- "secrets/**" # stores, private keys, cloud configs, and system directories —
- "/etc/**" # extend it with your own
enforcement:
restrictAfter: 3 # restrict the agent after this many policy violations
quarantineAfter: 5 # quarantine after this manyforbid.targets— hard deny; a match blocks the action. The generated policy already protects credential stores, private keys, cloud configs, and system directories; add your own.allow.targets— advisory for file/tool actions: a read or write outside it is logged as ascope_violationbut still runs (Sentinel governs the agent's tool calls; it does not sandbox the filesystem). Make it a hard deny withscopeBoundary: enforce.allow.networkHosts— a tool-levelnetwork_request(WebFetch/WebSearch) to an unlisted host is denied. Network made from inside a Bash command (curl,wget) is not gated here by default — seeegressScreen.enforcement.*— escalation thresholds plus the opt-in modes below.
Enforcement options
All optional, under enforcement:. Defaults change the agent's behavior as little as possible — the one exception is askOnDestructive, on by default because it restores a confirmation prompt the hook would otherwise suppress.
| Field | Values | Default | What it does |
| --- | --- | --- | --- |
| restrictAfter / quarantineAfter | integer | 3 / 5 | Escalate normal → restricted → quarantined after this many violations. |
| scopeBoundary | advisory | enforce | advisory | enforce denies out-of-scope file reads/writes (file/tool actions; Bash is not scope-checked). |
| egressScreen | off | warn | enforce | off | Screens shell network commands against allow.networkHosts; detection-first (logs interpreter/obfuscated egress rather than blocking it). |
| askOnDestructive | off | on | on | Routes a curated set of irreversible-destructive shell commands (literal args) to Claude Code's confirmation prompt. |
| unknownTools | warn | deny | warn | Disposition for unrecognized tool names. warn allows-and-logs; deny blocks, with allowUnknownTools: [<names>] as the escape hatch. |
What each option does and does not guarantee is detailed in SECURITY_MODEL.md.
Scope & security model
Sentinel is a guardrail with a verifiable record, not a sandbox. What that means precisely:
- Tamper-evident, not tamper-proof. The trail is hash-chained and signed, so tampering or a deleted entry is detectable and provable — not prevented. The value is proof.
- Cooperative, at the tool-call boundary. Enforcement happens where Claude Code asks to run a tool, via its hook — not at the OS. It enforces policy on a cooperative agent doing normal work; it is not adversarial containment.
- Same-host ceiling. Sentinel runs on the same machine as the agent, so a process determined to route around it on that host can (network from inside a Bash one-liner; killing the daemon). That's where the signed trail is the backstop — you can still detect and prove what happened. For prevention against a hostile process, pair Sentinel with OS/network-level isolation.
- Behavioral detection is observational. The baseline layer surfaces anomalies in reports; it does not block on them. Treat it as a signal to investigate, not an enforcement control.
Full threat model — what's defended, what's out of scope by design, and current limitations — is in SECURITY_MODEL.md.
If something's unexpectedly blocked
- A network host is blocked. The deny message names
allow.networkHosts— add the host there and re-run. - A file read or write is blocked or flagged out-of-scope. Check
forbid.targets(hard deny) andallow.targets(advisory by default; seescopeBoundary), and widen the relevant list. - The agent is restricted or quarantined. Repeated violations escalate it. Restore it with
sentinel release --agent=<id>— run it from a separate terminal if the agent's own Bash is blocked.
Turning it off / uninstalling
To turn Sentinel off in a project, remove its hooks:
sentinel disable claude-codeClaude Code then runs without Sentinel. This only removes Sentinel's hook entries from .claude/settings.local.json (your own hooks are preserved); the state in ~/.dahlia (audit trails, signing keys) is left in place — delete it manually for a full cleanup. Re-enable with sentinel init claude-code.
Uninstall in this order. Run
sentinel disable claude-codebeforenpm uninstall. If you uninstall the package while the hooks are still wired, Claude Code keeps invoking the now-orphaned hook — and with the gateway gone it fail-closes, blocking high-sensitivity tools (Bash/Write/Edit). Thesentinelcommand is gone at that point, so recover manually: delete the Sentinel hook entries (the ones runningnode ~/.dahlia/cc-hook.mjs …) from.claude/settings.local.json, and remove~/.dahlia/cc-hook.mjs.
License
Apache-2.0
