Railgun

Policy Engine

Deep dive into how Railgun inspects tool calls and enforces security policy.

The Policy Engine (rg-policy crate) is the heart of Railgun. It takes a tool call and returns a Verdict: Allow, Deny, or Ask.

Inspection Flow

Tool Call → Tool Check → Secret Scan → Command Check → Path Check → Network Check → Verdict

The first check that fails results in an immediate verdict.

1. Tool-Level Check

First, Railgun checks if the tool name matches any permission pattern:

// Deny patterns checked first
if tools.deny.iter().any(|p| glob_match(p, tool_name)) {
    return Verdict::Deny("Tool blocked by policy");
}
 
// Ask patterns
if tools.ask.iter().any(|p| glob_match(p, tool_name)) {
    return Verdict::Ask("Tool requires confirmation");
}
 
// Allow patterns skip further inspection
if tools.allow.iter().any(|p| glob_match(p, tool_name)) {
    return Verdict::Allow;
}

2. Secret Scanning

Railgun scans tool input for secrets:

Built-in Detectors

DetectorPattern
AWS Access KeyAKIA[0-9A-Z]{16}
GitHub Tokenghp_[a-zA-Z0-9]{36}, gho_..., ghs_...
OpenAI Keysk-[a-zA-Z0-9]{48}
Private Key-----BEGIN.*PRIVATE KEY-----
High EntropyShannon entropy > 4.5 for 20+ char strings

Scanning Behavior by Tool

ToolFields Scanned
Bashcommand
Writecontent
Editold_string, new_string
Taskprompt
WebFetchurl (for domains)

3. Command Pattern Matching

For Bash tool, Railgun checks the command against dangerous patterns:

// Check block patterns
for pattern in &policy.commands.block_patterns {
    if regex_match(pattern, command) {
        // Check if overridden by allow pattern
        if !policy.commands.allow_patterns.iter().any(|a| regex_match(a, command)) {
            return Verdict::Deny(format!("Dangerous command: {}", pattern));
        }
    }
}

Built-in Block Patterns

PatternMatches
rm\s+-rf\s+[/~]rm -rf /, rm -rf ~
:(){ :|:& };:Fork bomb
mkfs\.Disk format
dd\s+if=Raw disk write
chmod\s+777Dangerous permissions

4. Path Protection

For Read, Write, and Edit tools, Railgun checks if the path matches protected patterns:

for pattern in &policy.protected_paths.blocked {
    if glob_match(pattern, file_path) {
        return Verdict::Deny(format!("Protected path: {}", pattern));
    }
}

Built-in Protected Paths

  • **/.env, **/.env.*
  • **/*.pem, **/*.key
  • **/.ssh/**
  • **/.aws/credentials
  • **/.gnupg/**

5. Network Domain Checking

For Bash (URLs in commands) and WebFetch tools, Railgun checks for exfiltration domains:

for domain in &policy.network.block_domains {
    if url.host().contains(domain) {
        return Verdict::Deny(format!("Blocked domain: {}", domain));
    }
}

Fail-Closed Architecture

The Policy Engine wraps all inspection in a panic catcher:

pub fn inspect(input: &HookInput, policy: &RuntimePolicy) -> Verdict {
    panic::catch_unwind(panic::AssertUnwindSafe(|| {
        inspect_inner(input, policy)
    }))
    .unwrap_or_else(|_| Verdict::Deny("Internal error - fail closed"))
}

Any panic becomes a Deny verdict. This is intentional—security-critical code must never silently succeed when something goes wrong.

Verdict Types

pub enum Verdict {
    Allow,
    Deny { reason: String },
    Ask { message: String },
}
VerdictExit CodeBehavior
Allow0Tool executes normally
Ask0User prompted for confirmation
Deny2Tool blocked with reason

Performance

The Policy Engine is optimized for minimal overhead:

OperationTargetImplementation
Pattern matchingO(n) patternsPre-compiled regex at startup
Glob matchingO(n) patternsglob crate with caching
Secret detectionO(n) detectorsCompiled regex patterns
Total inspection< 1ms p99All patterns pre-compiled

Tool-Specific Inspection

Bash

struct BashInput {
    command: String,
}

Inspections:

  1. Command pattern matching (block/allow)
  2. Secret scanning in command
  3. URL extraction and network check

Write

struct WriteInput {
    file_path: String,
    content: String,
}

Inspections:

  1. Path protection check
  2. Secret scanning in content

Edit

struct EditInput {
    file_path: String,
    old_string: String,
    new_string: String,
}

Inspections:

  1. Path protection check
  2. Secret scanning in old_string and new_string

Read

struct ReadInput {
    file_path: String,
}

Inspections:

  1. Path protection check

WebFetch

struct WebFetchInput {
    url: String,
}

Inspections:

  1. Network domain check

MCP Tools

// Tool name format: mcp__<server>__<tool>
// e.g., mcp__github__create_issue

Inspections:

  1. Server-level permissions
  2. Tool-level permissions
  3. Parameter scanning based on tool type

Next Steps