Skip to content
zero-dep guardrail pipeline · node + edge

railguard

A guardrail pipeline for LLM apps and agents. When the model is fooled — and it will be — nothing important breaks.
Data-access guards and LLM I/O guards in one in-process lifecycle-hook pipeline.

human approvalescalated →onInputonPromptBuildbeforeToolCallafterToolCallonModelResponseonOutputBLOCKED
0runtime dependencies (CI-asserted)
6lifecycle hooks
101tests, both halves
5.9%reference-dataset ASR (honestly non-zero)
2×10OWASP 2026 coverage matrix

Per-rule tri-state rollout

enforce / observe / off. New rules shadow-run first, reconcile against the audit stream, then enforce — shipping is not a gamble.

Verdict ⊥ status

A crashed rule ≠ violating content. Per-rule failMode: open | closed; last-gate rules stay closed even when they crash.

Lethal-trifecta guard

Private data + untrusted content + outbound comms — all three present escalates to a human, with the raw underlying call, never a model-written summary.

Ed25519 signed audit chain

Tamper-evident hash chain: offline verification, torn-write truncation, chain continuation across restarts. WebCrypto — signs on edge too.

ASR + utility dual-metric evals

Interception rate alone drifts toward blocking everything. Versioned dataset + reference guards; CI pins the exact numbers.

Vercel AI SDK / Mastra adapters

Zero-dep structural typing: no peer dependencies; one line into wrapLanguageModel or inputProcessors.

The shape in thirty seconds

ts
import { createGuard, lens } from '@yiong/railguard'
import { faithfulness, injection, inputHygiene, linkPolicy } from '@yiong/railguard/rules'
import { consoleSink } from '@yiong/railguard/audit'

const guard = createGuard({
  audit: consoleSink(),
  hooks: {
    onInput: [inputHygiene(), injection({ mode: 'block' })],
    onOutput: [
      faithfulness({ resolve: (c) => corpus.slice(c), quoteOf: (c) => c.quote }),
      lens(linkPolicy({ allow: ['https://docs.example.com/'] }), (p) => p.answer, (p, v) => ({ ...p, answer: v })),
    ],
  },
})

const input = await guard.run('onInput', userQuestion, guard.context())
if (!input.ok) return refuse(input.blocked?.reason)

Design philosophy (OWASP GenAI 2026): the probabilistic layer (injection heuristics) alone is not a security boundary — the boundary is output-side verification and deterministic rules. That is a documented commitment of this package, not a disclaimer.