v0.41.35.0 feat(guardrails): vendor-neutral content guardrail seams (supersedes #1652)#1660
Merged
Conversation
Expose observe-only guardrail seams at the five boundaries where external
content enters the retrieval layer and the LLM gateway, so a content firewall
(prompt-injection / RAG-poison detector, PII scrubber, etc.) can be hooked in
without binding GBrain to any specific vendor.
New module src/core/guardrails.ts:
- runGuardrails({ hook, content, metadata }) -> void
- registerGuardrailProvider / unregisterGuardrailProvider
- hasGuardrails() fast-path guard for hot paths
Seams (all observe-only, fail-open, inline-await, inert by default):
- file_storage.markdown (import-file.ts importFromContent)
- file_storage.code (import-file.ts importCodeFile)
- ai_gateway.chat (gateway.ts chat, last user message only)
- ai_gateway.expand (gateway.ts expand)
- ai_gateway.tool_input (gateway.ts toolLoop, before pending-persist)
Invariants enforced by test/guardrails.test.ts (14 tests):
- returns void; callers never branch on a verdict
- provider throw/reject is swallowed (fail-open isolation)
- slow async provider is awaited before resolving (inline)
- zero providers => no-op; empty/blank content short-circuits
- content + metadata passed through unmutated; idempotent by id
Hooks pass only the ingest/user-facing payload (md/code body, last user
message, expansion query, tool input). Never system prompts, full history,
tool output, LLM output, embeddings, or multimodal payloads.
Docs: docs/guardrails.md (contract, seam table, provider authoring guide).
OSS ships inert; vendors register a provider in their own package.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d795be4 to
27b1116
Compare
…-seam # Conflicts: # CHANGELOG.md # VERSION # package.json
…-seam # Conflicts: # CHANGELOG.md # VERSION # package.json
…-seam # Conflicts: # CHANGELOG.md # VERSION # package.json
mgunnin
added a commit
to mgunnin/gbrain
that referenced
this pull request
Jun 3, 2026
* upstream/master: v0.41.36.0 feat(mcp): publish agent skills (list_skills / get_skill) for thin clients (garrytan#1661) v0.41.35.0 feat(guardrails): vendor-neutral content guardrail seams (supersedes garrytan#1652) (garrytan#1660) v0.41.34.0 feat(search): retrieval cathedral — max-pool + title + alias + evidence (garrytan#1657) v0.41.33.0 feat(search): intent-aware adaptive return-sizing + agent-facing query param (garrytan#1640) v0.41.32.0 fix(staleness): commit-relative sync staleness (supersedes garrytan#1623) (garrytan#1656) v0.41.31.0 feat(embed): delta-aware sync --all cost gate + real stale-embedding semantics (garrytan#1632) v0.41.30.0 fix(brainstorm/lsd): --save writes the advertised .md file via canonical ingestion path (garrytan#1655) # Conflicts: # src/core/operations.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds vendor-neutral content guardrail seams at the five boundaries where external content enters GBrain's retrieval layer and LLM gateway, so a content firewall (prompt-injection / RAG-poison detector, PII scrubber, etc.) can be hooked in without binding GBrain to any specific vendor.
Supersedes #1652 — rebased from
garrytan-agentsinto a base-repo branch so CI gets secret access (per CLAUDE.md's "Checking out PRs from garrytan-agents" workflow). Original authorship preserved via cherry-pick; the feature commit is unchanged.The seam, not the vendor. GBrain gets a generic
runGuardrails({ hook, content, metadata })interface with five hook points. Zero guardrails registered by default — the OSS ships completely inert. No vendor code, no API URL, no phone-home in the public tree. A provider (Silmaril or anyone) implementsclassify()in their own package and registers it at init.Five hooks, all observe-only / fail-open:
file_storage.markdown+file_storage.code— the import-into-retrieval boundary (the RAG-poison chokepoint)ai_gateway.chat/.expand/.tool_input— GBrain's own LLM callsHard invariants, test-enforced (14 tests):
runGuardrailsreturnsvoidso nobody can branch on a verdict; a throwing provider can't break an ingest; slow providers are awaited inline; empty content short-circuits. The existing import-file tests still pass — the hot path is undisturbed.Closes the CVE gap. The injection-via-filesystem attack bypassed the old LLM-only hooks because content entered through the file boundary.
file_storage.markdownis a real hook at that boundary now — the seam and the vuln fix are the same move.Commits:
feat(guardrails): vendor-neutral content guardrail seams(garrytan-agents) — the module, five hooks, doc, testschore: bump version and changelog (v0.41.34.0)Files
src/core/guardrails.ts(new) —runGuardrails/registerGuardrailProvider/unregisterGuardrailProvider/hasGuardrails. Snapshot-before-iterate; per-provider try/catch.src/core/import-file.ts—file_storage.markdown(after parseMarkdown + size guard, before sanity/hash/chunk/embed/write) +file_storage.code(after code size guard, before hash/chunk/embed/write).src/core/ai/gateway.ts—ai_gateway.chat(latest user message only),ai_gateway.expand(query),ai_gateway.tool_input(tool name + input, before pending-persist). Cycle-safe stringifier.docs/guardrails.md(new) — contract, seam table, provider-authoring guide.test/guardrails.test.ts(new) — 14 tests pinning the contract.Test Coverage
The seam is a new isolated module —
test/guardrails.test.ts(14 tests) covers every invariant: observe-only (voidreturn), fail-open (throw + reject isolation), inline await (slow async provider awaited), inert-by-default (zero providers = no-op), empty/blank short-circuit, register/unregister idempotency byid, content+metadata passthrough unmutated.The five hook call sites are guarded by
hasGuardrails()and wrapped fail-open, so with no provider registered they are pure no-ops — verified by the existing import-file suite (25 pass) and full typecheck.Pre-Landing Review
bun run verify— 29/29 checks green.bun run typecheckclean.bun test test/guardrails.test.ts14/14 pass.bun test test/import-file.test.ts25/25 pass (ingest hot path undisturbed).Documentation
docs/guardrails.mdis the deliverable handed to integration partners: the contract (five hard invariants), the seam table, the provider-authoring guide, and a shadow-mode provider sketch. A partner's shadow-mode firewall becomes ~80 lines of provider code that never touches this public repo.Test plan
bun run verify— 29/29 greenbun run typecheck— cleanbun test test/guardrails.test.ts— 14/14 passbun test test/import-file.test.ts— 25/25 pass (hot path undisturbed)🤖 Generated with Claude Code