Package Exports
- llm-stream-guard
Readme
llm-stream-guard
Security filter for LLM streams — redact secrets and PII, enforce tool-call policy, sanitize errors. Works on raw bytes (TransformStream) and parsed event streams. Declarative JSON/YAML policies and a CLI for offline scans.
A standalone, zero-dependency TypeScript security filter for LLM proxy and agent pipelines. Byte mode: chunk-safe secret redaction on raw SSE. Event mode: tool allow/deny, arg blocking, PII & error sanitization on parsed streams. Policy files +
llm-stream-guard scanfor CI prep.
Status: Stable 0.3.0 — declarative policy loader, built-in profiles, and CLI (validate, resolve, scan, diff). Review CHANGELOG.md before upgrades.
Contents
- Why stream guard?
- Two modes
- Architecture
- GuardEvent model
- Violation modes
- Install
- First success in 30 seconds
- Quickstart
- Policy files & CLI
- Mode decision guide
- Documentation
- How this compares
- Non-goals
- Development
Why stream guard?
When proxying or running agents, unsafe content leaks downstream in predictable ways:
- Secrets in text deltas — API keys, bearer tokens, JWTs echoed in model output.
- Dangerous tool args — shell injection, exfil URLs, oversized JSON before execution.
- Unauthorized tool names — models invoke tools outside your allowlist.
- Raw provider errors — internal URLs and stack traces forwarded to browsers.
Many filters scan raw bytes only and miss precise policy on assembled tool_call.done JSON. This library targets both byte and event modes with zero runtime dependencies.
- Mid-chunk splits — secrets split across TCP reads use a rolling buffer + prefix holdback (LSG-C).
- Tool policy timing — evaluate names early; validate args on
donewhen JSON is complete (LSG-T). - Violation modes —
block,warn, orauditwithonViolationfor SIEM-friendly logs.
Two modes
| Mode | API | When |
|---|---|---|
| Byte | createByteGuard() |
Proxy forwards provider-shaped SSE without parsing |
| Event | guardEvents() |
Parsed stream — assemble, AI SDK, or custom mapper |
Architecture
Raw upstream content enters through byte guard or event guard; composable rules redact or block before your proxy, UI, or tool executor sees output.
Optional pairing with llm-stream-assemble (parse → guard) — cookbook only, no npm coupling:
Lifecycle and concurrency
Create one GuardContext per stream — never share across concurrent requests. Stateless helpers (pipeGuard, internal transform pipeline) compose into stateful entry points.
Diagram sources: docs/img/ (Mermaid .mmd + committed SVG). Regenerate with pnpm diagrams:build.
GuardEvent model
Independent event union — not StreamEvent, not provider types:
| Type | Shape |
|---|---|
text |
{ type, phase: delta | done, text } |
tool_call |
{ type, phase, id?, name?, args?, argsText? } |
reasoning |
{ type, phase, text } |
error |
{ type, message, code? } |
finish |
{ type, reason? } |
Full spec: docs/proposal.MD.
Violation modes
| Mode | Secrets / PII | Tool policy |
|---|---|---|
block |
Always redact | Safe substitute + policy_violation finish |
warn |
Always redact | Block tool + onViolation |
audit |
Always redact + onViolation on match |
Pass tool through + onViolation |
Install
pnpm add llm-stream-guard
# or npm install llm-stream-guardRequirements: Node.js 18+ · Bun / Deno / Workers (Web Streams)
Maintainers: run pnpm release:prep before tagging and npm publish. GitHub Release notes from CHANGELOG.md.
First success in 30 seconds
git clone git@github.com:01laky/llm-stream-guard.git
cd llm-stream-guard
pnpm install
./scripts/setup-githooks.sh
pnpm verifyThen pipe bytes through the byte guard:
import { createByteGuard } from "llm-stream-guard";
const guarded = sourceStream.pipeThrough(createByteGuard({ redactSecrets: true, mode: "warn" }));Quickstart
Proxy (byte mode)
import { createByteGuard } from "llm-stream-guard";
return new Response(
upstream.body!.pipeThrough(
createByteGuard({ redactSecrets: true, sanitizeErrors: true, mode: "warn" }),
),
{ headers: { "Content-Type": "text/event-stream" } },
);redactSecrets and sanitizeErrors are active on createByteGuard() options.
Agent (event mode)
import {
allowTools,
blockToolArgs,
guardEvents,
redactSecrets,
sanitizeErrors,
} from "llm-stream-guard";
for await (const event of guardEvents(
parsedEvents,
{ mode: "block", onViolation: (v) => console.warn(v.rule, v.message) },
redactSecrets(),
allowTools(["search", "read_file"]),
blockToolArgs(/rm\s+-rf/),
sanitizeErrors(),
)) {
if (event.type === "tool_call" && event.phase === "done") {
await executeTool(event);
}
}Transform ordering
Recommended pipeline:
redactSecrets() → redactPII()? → allowTools/denyTools → blockToolArgs → maxToolArgsBytes → sanitizeErrors()Reversing order is explicit — see docs/integration-cookbook.md.
Policy files & CLI
Declarative policies map to the same rule factories as manual stacks. Built-in profiles: proxy-strict, agent-gate, audit-only.
Policy file (policies/agent-gate.json)
{
"version": "1",
"policyVersion": "team-alpha-v3",
"mode": "block",
"rules": [
{ "allowTools": { "names": ["search", "read_file", "grep"] } },
{ "maxToolArgsBytes": { "max": 65536 } },
{ "sanitizeErrors": {} }
]
}Programmatic (loadPolicy / createGuardFromPolicy)
import { createGuardFromPolicy, loadPolicy } from "llm-stream-guard";
const guard = createGuardFromPolicy(loadPolicy("./policies/agent-gate.json"));
for await (const event of guard.guard(parsedEvents)) {
await handle(event);
}
const byteGuard = guard.createByteGuard();CLI
npx llm-stream-guard validate policies/agent-gate.json
npx llm-stream-guard resolve policies/examples/extends-agent.json
npx llm-stream-guard scan --policy policies/agent-gate.json test/fixtures/events/
cat capture.log | npx llm-stream-guard scan --policy policies/proxy-strict.json -
npx llm-stream-guard diff policies/v1.json policies/v2.json --check
npx llm-stream-guard profiles list| Env variable | Effect |
|---|---|
GUARD_MODE |
Override policy mode (block / warn / audit) |
GUARD_POLICY_PATH |
Default --policy path for CLI scan |
Schema reference: schemas/policy-v1.json. Example policies: policies/.
Policy pitfalls: overlapping allow/deny lists (POLICY_E009); empty allowlist with mode: block (POLICY_E010 / POLICY_E008).
Mode decision guide
Pick byte vs event mode in ~30 seconds:
Use the modes diagram above, or:
- Raw SSE to browser, no parser →
createByteGuard() - Tool gate before execute →
guardEvents()+ rule factories - Parse with assemble / AI SDK first → map to
GuardEvent, thenguardEvents()
Documentation
- Product & technical proposal
- Testing strategy
- Publishing checklist (maintainers)
- Architecture diagrams
- How this compares
- Integration cookbook
- FAQ
- Contributing
Related: llm-stream-assemble — stream parsing and assembly (separate package).
How this compares
| llm-stream-guard | Enterprise middleware | llm-stream-assemble | |
|---|---|---|---|
| Scope | Stream security filter | Broad platform | Stream parsing |
| Byte + event | Both first-class | Often bytes-only | Events (after parse) |
| Tool policy | First-class | Varies | Assembly only |
| Dependencies | Zero runtime | Varies | Zero runtime |
Full matrix: docs/comparison.md.
Non-goals
- No HTTP client, auth, or agent loop
- No tool execution or UI components
- No LLM-as-judge classifier
- No hard dependency on assemble, AI SDK, or LangChain
- No provider adapters (use assemble or your parser)
See docs/proposal.MD.
Development
pnpm install
./scripts/setup-githooks.sh
pnpm verify| Command | Description |
|---|---|
pnpm verify |
format + typecheck + build + test + fixtures + smoke |
pnpm verify:deps |
fail if runtime dependencies are added |
pnpm release:prep |
pre-tag checks (version, CHANGELOG, dist, npm pack) |
pnpm diagrams:build |
regenerate README SVGs from Mermaid sources |
pnpm fixtures:check-policies |
validate example + profile policies |
pnpm fixtures:audit-policy-registry |
policy fixture REGISTRY parity |
pnpm test |
Vitest (LSG-S/B/E/C/R/T/P/POL, LSG-REL) |
pnpm bench:smoke |
local byte/event timing (informational) |
pnpm build |
tsup → ESM + CJS + declarations |
Author
Ladislav Kostolny — 01laky@gmail.com · GitHub @01laky
License
MIT — see LICENSE. Copyright (c) 2026 Ladislav Kostolny.