README.md

April 27, 2026 · View on GitHub

Drawbridge Logo

Session-aware content sanitization pipeline powered by ClawMoat.
Standalone library — wire into any agent pipeline. 460 library tests + 199 plugin tests, security-hardened.


What Drawbridge Does

ClawMoat detects threats in a single scan. Drawbridge wraps it in a session-aware pipeline: threshold-based blocking, syntactic pre-filtering, exponential-decay frequency tracking with three escalation tiers, content redaction, deployment-specific context profiles, structured audit trails, and cross-session alert rules.

Every module is standalone. Use them individually or wire them together.

Install

npm install @vigil-harbor/clawmoat-drawbridge clawmoat

Modules

Scanner

Wraps ClawMoat with threshold filtering, direction-aware scanning (inbound/outbound/both), and a finding callback for observability.

Usage
import { DrawbridgeScanner } from "@vigil-harbor/clawmoat-drawbridge";

const scanner = new DrawbridgeScanner({
  blockThreshold: "medium",
  direction: "inbound",
});

const result = scanner.scan("some user input");
if (!result.safe) {
  console.log("Blocked:", result.blockingFindings);
}

Syntactic Pre-Filter

Pure-function pattern matching — catches injection phrases, structural anomalies, encoding tricks. 17-rule frozen taxonomy with NFKC normalization, zero-width character stripping, and extended homoglyph detection. No model calls, sub-millisecond.

Usage
import { PreFilter } from "@vigil-harbor/clawmoat-drawbridge";

const filter = new PreFilter();
const result = filter.run(content);
if (!result.pass) {
  console.log("Blocked by rules:", result.ruleIds);
}

Frequency Tracker

Per-session exponential-decay suspicion scoring with rolling window counter. Findings accumulate, scores decay over time. Three escalation tiers: forced deep inspection → enhanced scrutiny → session termination. Rolling counter prevents low-and-slow evasion of exponential decay. Session creation rate-limited at capacity to prevent flood-eviction attacks.

Usage
import { FrequencyTracker } from "@vigil-harbor/clawmoat-drawbridge";

const tracker = new FrequencyTracker();
const result = tracker.update(sessionId, scanResult.findings.map(f => f.ruleId));
if (result.terminated) {
  // Session killed — tier3 exceeded
}

Content Sanitize

Position-based redaction with overlap merging. Strips or replaces matched content from scanner findings. Configurable placeholders, blocked-only or redact-all modes. Optional HMAC-SHA256 content hashing for redaction audit trails (no bare SHA-256 ever emitted).

Usage
const { sanitized, safe } = scanner.scanAndSanitize(content);
// sanitized.sanitized → redacted content
// sanitized.redactionCount → number of replacements

// With HMAC hashing for audit correlation:
const result = sanitizeContent(content, findings, {
  hashRedactions: true,
  hmacKey: process.env.REDACTION_HMAC_KEY,
});
// result.redactions[0].contentHash → HMAC-SHA256 hex string

Context Profiles

Deployment-specific tuning. Five built-in profiles (general, customer-service, code-generation, research, admin) that adjust pre-filter emphasis and frequency thresholds. Custom profiles supported.

Usage
import { ProfileResolver, PreFilter, FrequencyTracker } from "@vigil-harbor/clawmoat-drawbridge";

const profile = new ProfileResolver("admin");
const filter = new PreFilter(profile.applySyntacticConfig());
const tracker = new FrequencyTracker(profile.applyFrequencyConfig());
// Admin: lower escalation thresholds, stricter frequency weights

Audit Emitter

Structured event emission gated by four verbosity tiers (minimal → standard → high → maximum). Callback-based — no file I/O, no opinions on storage.

Usage
import { AuditEmitter } from "@vigil-harbor/clawmoat-drawbridge";

const audit = new AuditEmitter({
  verbosity: "standard",
  onEvent: (event) => myLogger.write(event),
});

Alert Manager

Evaluates audit events against configurable rules. Cross-session burst detection, frequency escalation alerts, scanner/pre-filter correlation. Critical alerts exempt from rate limiting. Config validated at construction. Tier 3 alerts cannot be disabled.

Usage
import { AlertManager, AuditEmitter } from "@vigil-harbor/clawmoat-drawbridge";

const alerts = new AlertManager({
  onAlert: (alert) => pagerduty.send(alert),
});

const audit = new AuditEmitter({
  onEvent: (event) => {
    myLogger.write(event);
    alerts.evaluate(event);
  },
});

Tool Call Policy Guard

Security gating for tool invocations. Wraps ClawMoat's evaluateTool() with parameter content scanning via the Drawbridge pipeline, frequency-aware escalation (warn→block at tier1+), and session-state enforcement. Two-stage tool name normalization strips server prefixes and maps aliases. Fail-open on error.

Usage
import { ToolCallGuard, DrawbridgePipeline, FrequencyTracker } from "@vigil-harbor/clawmoat-drawbridge";

const guard = new ToolCallGuard({
  pipeline,
  tracker,
  engine: clawmoatInstance,  // optional — duck-typed for evaluateTool()
  exemptTools: ["read"],
  restrictedTools: ["read"],
  escalateWarnings: true,
  scanParams: true,
});

const result = guard.evaluate({
  toolName: "exec",
  params: { command: "rm -rf /" },
  sessionId: "session-123",
});

if (result.block) {
  console.log("Blocked:", result.blockReason);
}
// result.audit contains structured metadata for audit trails

Schema Validator

Validates MCP tool output against registered schemas with discriminated union support. Colon-namespaced keys (serverName:toolName), prototype-pollution-safe field checks, and fail-closed defaults. Runs as a standalone module or wired into the pipeline.

Usage
import { SchemaValidator } from "@vigil-harbor/clawmoat-drawbridge";

const validator = new SchemaValidator({
  enabled: true,
  toolSchemas: {
    "my-server:my-tool": {
      discriminant: "type",
      variants: {
        success: { required: ["data"], fields: { data: "string" } },
        error: { required: ["message"], fields: { message: "string" } },
      },
    },
  },
});

const result = validator.validate(toolOutput, "my-server", "my-tool");
if (!result.pass) {
  console.log("Schema violations:", result.violations);
}

Pipeline (v1.1)

Single inspect() call that orchestrates every stage: trust check, pre-filter, schema validation, two-pass gate, scanner, frequency tracking, sanitize, audit emission, and alert evaluation. Returns a unified PipelineResult with safety verdict, redacted content, audit events, and alerts.

  • Trust tier routing — trusted MCP servers bypass inspection (schema validation still runs)
  • Schema validation — MCP tool outputs validated against registered schemas; hard-blocked content skips schema
  • Two-pass gating — hard-block pre-filter rules skip the scanner; prior session suspicion can force the scanner back on
  • HMAC redaction hashing — opt-in keyed hashes on redacted content for audit correlation (no bare SHA-256)
  • Terminated session fast-path — tier3 sessions are blocked immediately without re-inspection
  • Profile-driven tuning — profile applied at construction, tunes pre-filter and frequency tracker
  • Trusted tool alertstrustedToolSchemaFail fires high-severity alerts when trusted servers emit malformed output
  • Input normalization — NFKC normalization, zero-width stripping, homoglyph mapping applied before pattern matching
  • Validation hooksvalidateSessionId and validateServerName callbacks for transport-layer identity verification
  • Module accessorsresolvedProfile, getSessionState(), getAuditStats(), getAlertStats(), resetSession(), clear()
Usage
import { DrawbridgePipeline } from "@vigil-harbor/clawmoat-drawbridge";

const pipeline = new DrawbridgePipeline({
  profile: "admin",
  trustedServers: ["local-filesystem"],
  twoPass: { enabled: true },
  schema: {
    enabled: true,
    toolSchemas: { "my-server:my-tool": myToolSchema },
  },
  sanitize: {
    hashRedactions: true,
    hmacKey: process.env.REDACTION_HMAC_KEY,
  },
  audit: {
    verbosity: "high",
    onEvent: (event) => console.log(JSON.stringify(event)),
  },
  alerting: {
    onAlert: (alert) => sendToSlack(alert),
  },
  // Transport-layer identity verification (recommended)
  validateServerName: (name) => verifiedMcpServers.has(name),
  validateSessionId: (id) => sessionStore.has(id),
});

const result = pipeline.inspect({
  content: userInput,
  source: "transcript",
  sessionId: "session-123",
  messageId: "msg-456",
});

if (!result.safe) {
  console.log("Blocked:", result.scanResult?.blockingFindings);
  console.log("Sanitized:", result.sanitizedContent);
}

if (result.terminated) {
  // Session killed — drop the connection
}

Architecture

  ┌─────────────────────────────────────────────────┐
  │              ToolCallGuard (v1.3)               │
  │  before_tool_call → evaluate(toolName, params)  │
  │  session check → exempt → param scan → policy   │
  │  → frequency escalation → audit + alert         │
  └─────────────────────────────────────────────────┘

                    ┌─────────────────────────────────────┐
                    │           DrawbridgePipeline        │
                    │            inspect(input)           │
                    └──────────────┬──────────────────────┘

                    ┌──────────────▼──────────────────────┐
                    │         Trust Check                 │
                    │   trusted server? → fast-path exit  │
                    │   (schema validation still runs)    │
                    └──────────────┬──────────────────────┘

                    ┌──────────────▼──────────────────────┐
                    │     Syntactic Pre-Filter            │
                    │   regex patterns, structural checks │──── findings  ────┐
                    └──────────────┬──────────────────────┘                   │
                                   │                                          │
                    ┌──────────────▼──────────────────────┐                   │
                    │      Two-Pass Gate                  │                   │
                    │  hard block? skip scanner + schema  │                   │
                    │  (frequency override can force it)  │                   │
                    └──────────────┬──────────────────────┘                   │
                                   │                                          │
                    ┌──────────────▼──────────────────────┐                   │
                    │   Schema Validator (MCP only)       │                   │
                    │   discriminated unions, field types │                   │
                    └──────────────┬──────────────────────┘                   │
                                   │                                          │
                    ┌──────────────▼──────────────────────┐                   │
                    │     Scanner (ClawMoat)              │                   │
                    │   prompt injection, PII, secrets    │──── findings  ────┤
                    └──────────────┬──────────────────────┘                   │
                                   │                                          │
                    ┌──────────────▼──────────────────────┐    ┌─────────────▼──────────┐
                    │         Sanitize                    │    │  Frequency Tracker      │
                    │   redact blocked content            │    │  decay scoring, tiers   │
                    │   (HMAC hashing opt-in)             │    └─────────────────────────┘
                    └──────────────┬──────────────────────┘

                    ┌──────────────▼──────────────────────┐
                    │       Audit Emitter                 │
                    │  verbosity-gated structured events  │
                    └──────────────┬──────────────────────┘

                    ┌──────────────▼──────────────────────┐
                    │       Alert Manager                 │
                    │  rules → onAlert callback           │
                    │  (trustedToolSchemaFail alert)      │
                    └─────────────────────────────────────┘

All modules are standalone — use individually or together. Context profiles tune the pre-filter and frequency tracker at construction time. Callback-based delivery throughout: no file I/O, no network calls.

Module Status

ModuleVersionTestsStatus
Scannerv0.120✅ Implemented + audited
Frequency Trackerv1.134✅ Rolling window, eviction hardening
Pre-Filterv1.147✅ NFKC normalization, 17-rule taxonomy
Normalizationv1.129✅ Zero-width, homoglyph, RTL detection
Schema Validatorv1.117✅ Implemented + hardened
Profilesv1.124✅ Deep-frozen resolved profiles
Sanitizev1.138✅ HMAC hashing, overlap merge
Audit Emitterv1.347✅ Config validation, verbosity gating
Alert Managerv1.344✅ Critical exempt, error boundary
Tool Call Guardv1.324✅ Policy eval, param scan, escalation
Security Audit45✅ 20 findings, all addressed
Pipelinev1.162✅ Validation hooks, defensive copies
Hardening Tests25✅ Pass 3 coverage
OpenClaw Pluginv1.3199✅ Hook-only, fail-open, tool guard

Security

Adversarial security review completed across all modules. 20 findings identified and addressed across three hardening passes.

Original audit (v1.0)

IDSeverityDescription
S1.4CriticalisSeverity prototype chain bypass — block threshold evasion
S6.1HighAudit emitter spread order — timestamp and event type forgery
S1.3MediumnormalizeRuleId namespace collision with reserved prefixes
S1.6MediumonFinding callback exceptions broke scan loop
S5.1MediumOut-of-bounds position produced phantom redactions
X3aMediumSYNTACTIC_RULES shallow freeze — nested arrays mutable
X3bMediumEVENT_MIN_VERBOSITY not frozen

v1.1 Hardening

Schema & HMAC (pre-audit)

  • HMAC-SHA256 redaction hashing (no bare SHA-256), prototype pollution guards, schema key namespace validation, deep-cloned toolSchemas, double-violation prevention, schema skipped on hard-blocked content, safe vs schemaResult.pass semantic separation

Pass 1 — Input normalization (Findings #1-4)

  • NFKC normalization before all injection pattern matching
  • Zero-width character stripping with escalation when combined with injection patterns
  • Extended homoglyph map (Greek, Latin Extended)
  • RTL override detection

Pass 2 — Defensive copies & immutable exports (Findings #11-13, #17)

  • Spread-copy config arrays in pipeline constructor
  • deepFreeze / Object.freeze on all exported constants
  • Frozen resolved profile properties

Pass 3 — Alerting & frequency hardening (Findings #6-10, #15, #18-19)

  • Config validation at construction (audit verbosity, alert manager params)
  • Critical alerts exempt from rate limiting
  • evaluate() error boundary — never throws on malformed events
  • Rolling window counter prevents low-and-slow evasion of exponential decay
  • Session creation rate-limited at capacity; terminated sessions evicted first
  • validateSessionId / validateServerName hooks for transport-layer verification

Tier 3 frequency alerts cannot be disabled — the constructor enforces tier3Enabled: true regardless of consumer config.

Security Considerations

sessionId and serverName in PipelineInput are caller-provided and unvalidated by default:

  • sessionId must be derived from authenticated transport state (e.g., server-signed session token). If sourced from client input, attackers can poison other sessions' frequency scores. Configure validateSessionId for runtime enforcement.
  • serverName must be verified at the transport layer (e.g., mTLS, signed tokens). If sourced from message content, attackers can spoof trusted servers to bypass all inspection. Configure validateServerName for runtime enforcement.

See DrawbridgePipelineConfig for the validation callback signatures.

License

MIT — Built by Vigil Harbor, LLC