OmniRoute Guardrails 实战指南:请求/响应双阶段护栏链、PII 脱敏与 Prompt 注入防护的源码级解析
OmniRoute Guardrails 实战指南请求/响应双阶段护栏链、PII 脱敏与 Prompt 注入防护的源码级解析【免费下载链接】OmniRouteNever stop coding. Free MIT AI gateway: one endpoint, 352 providers (150 free), 1200 models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline Copilot. Quota-aware auto-fallback, RTKCaveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550 contributors项目地址: https://gitcode.com/GitHub_Trending/om/OmniRouteGuardrails护栏是 OmniRoute 中位于网关与上游 Provider 之间的一道安全与内容治理层每个 guardrail 都可以在preCall请求发出前与postCall上游响应返回后两个阶段检查、拦截、改写或标注 payload。本文基于仓库中的安全文档 docs/i18n/pl/docs/security/GUARDRAILS.md英文母版 docs/security/GUARDRAILS.md与src/lib/guardrails/的真实实现展开读完你可以掌握内置 guardrail 的优先级编排与执行顺序、fail-open 设计的源码依据、Prompt 注入检测的模式与 16 KB 扫描窗口、PII/凭据脱敏的开关矩阵以及如何编写并注册一个自定义 guardrail。一、核心设计双阶段检查与 fail-open 原则从源码结构看整个体系由三部分组成基础契约BaseGuardrail与GuardrailContext/GuardrailResult类型定义于 base.ts单例注册表guardrailRegistry负责排序、去重替换、执行链与失败降级定义于 registry.ts一批内置 guardrail 实现vision/audio/video bridge、PII、凭据、注入防护统一从 index.ts 导出。系统整体是fail-open故障放行的任何一个 guardrail 执行时抛出异常registry 只会把错误记录为该 guardrail 的执行结果error: message并打logger.warn然后继续执行下一个 guardrail而不会让请求本身失败。这一点可以在 registry.ts 的catch分支中直接验证} catch (error) { if (context.signal?.aborted) { throw new Error(Guardrail processing aborted); } const message error instanceof Error ? error.message : String(error); results.push({ blocked: false, error: message, guardrail: guardrail.name, ... }); logger.warn?.(GUARDRAIL, ${guardrail.name} pre-call failed open, { error: message }); }与之相对拦截block永远是显式决策——只有 guardrail 主动返回block: true才会中断链条异常绝不会被当成拦截。这种“异常放行、拦截显式”的划分保证了安全组件自身出故障时主链路可用性不受牵连。默认加载顺序registry 在模块导入时通过registerDefaultGuardrails()自动注册全部内置 guardrail见 registry.ts。当前仓库版本v3.8.51内置六个按优先级升序执行数字越小越先跑PriorityNameStage(s)File5vision-bridgepreCallvisionBridge.ts6audio-bridgepreCallaudioBridge.ts7video-bridgepreCallvideoBridge.ts10pii-maskerprepostpiiMasker.ts20prompt-injectionpreCallpromptInjection.ts95credential-maskerprepostcredentialMasker.ts波兰语文档v3.8.40记录的是当时三个内置 guardrailvision-bridge、pii-masker、prompt-injection的版本后续版本先后加入了 audio-bridge、video-bridge 与 credential-masker本文以当前仓库代码为准。register()的实现registry.ts会按归一化名称替换同名旧 guardrail并重新按priority升序排序——这意味着你可以在运行时用同名类覆盖一个内置 guardrail。二、内置 guardrail 逐一解析2.1 Vision Bridgepriority 5仅 preCall它拦截那些“带着图片、但目标模型不支持视觉”的请求在调用上游之前用可配置的 vision 模型把图片部分替换成文本描述[Image N]: description或整体改路由到具备视觉能力的模型从而让 text-only provider 透明地处理多模态 payload。执行流程对应 docs/security/GUARDRAILS.md 的 Vision Bridge 一节目标模型已支持 vision 时直接跳过除非它在 forced-bridge 名单isVisionBridgeForcedModel中通过extractImageParts(messages)抽取顶层图片部分委托给统一的媒体检测器detectMediaParts()覆盖 OpenAIimage_url、Anthropic base64/URL source 与 Responses APIinput_image等形态抽不到则跳过经resolveVisionBridgeRuntimeSettings()解析运行配置新modalityBridgeVision*设置键优先旧visionBridge*键保留一个发布周期的回退读取定义在 modalityBridgeDefaults.ts遗留常量在 visionBridgeDefaults.ts模式选择器modalityBridgeVisionMode决定 reroute只换model字段meta 带rerouted/fromModel/toModel/imagesKept还是 describe截断到maxImages张Promise.allSettled并行调用 vision 模型失败图片按约定保留原图或替换为 unavailable 占位返回modifiedPayload metaimagesProcessed、descriptions、processingTimeMs、visionModel。三个模式的行为对比ModeDefault行为auto✔旧启发式非 combo/auto/模型在无可用凭据时 reroute 到最佳 vision 模型有凭据则 describecombo 目标一律 describedescribe永远 describe用户选的模型永远作答reroute强制 reroute但仍受“目标凭据守卫”约束没有可用 vision 目标时回落到 describe原始图片绝不发给 text-only 后端配套能力还包括任务感知的 describe 提示词默认modalityBridgeVisionTaskAwaretrue把最后一条用户消息截到 500 字符附加进描述提示、modalityBridgeVisionMaxChars默认0不截断合法区间0或 100–50000、进程内 LRUTTL 描述缓存键为sha256(imageRef composedPrompt configuredBridgeModel)失败不缓存、以及透明性响应头x-omniroute-modality-bridge: image-text;modelvisionModel;partsn由buildModalityBridgeHeader()构造。相关测试散布在 tests/unit/guardrails/ 目录如 visionBridge.test.ts、visionBridgeHelpers.extractImageParts.test.ts。2.2 Audio / Video Bridgepriority 6 / 7仅 preCallAudio BridgeaudioBridge.ts拦截携带音频但目标能力不明的聊天请求从不改路由——音频部分经本地/v1/audio/transcriptionsself-loop 转成文本替换为[Audio N]: transcript后由原聊天模型继续作答。能力判定走getResolvedModelCapabilities()显式false或无证据的null都会触发保守桥接true则旁路。运行设置DB 支撑、Zod 校验modalityBridgeAudioEnabled默认true、modalityBridgeAudioModel空 自动在AUDIO_TRANSCRIPTION_PROVIDERS中选首个有可用凭据的 STT 模型、modalityBridgeAudioTimeout默认 600001000–300000、modalityBridgeAudioMaxClips默认 31–10。单个转写失败保留原音频部分全部失败且目标被证实supportsAudio false时替换为显式 unavailable 占位。Video BridgevideoBridge.ts、videoBridgePipeline.ts默认关闭modalityBridgeVideoEnabledfalse因为 FFmpeg/ffprobe 是可选运行时依赖且抽帧字幕化会增加延迟与模型成本。它支持input_video、video_url、video_source、HTTPS URL 与data:video/*;base64形态经内部受控 brokerPOST /api/modality-bridge/video/extractLOCAL_ONLY 受信任回环用 ffprobe/ffmpeg 抽取 1–16 帧 JPEG长边缩放到 ≤1024px再逐帧交给配置的 Video 模型空则继承 Vision 设置打字幕最终用带[Video description:前缀的不可信媒体观察文本替换原始视频部分。关键运行设置modalityBridgeVideoAnalysisModefull/focused、modalityBridgeVideoFrameCount默认 81–16、modalityBridgeVideoSamplingPolicyuniform/scene_aware/segment_aware检测器失败确定性回退uniform、modalityBridgeVideoMaxVideos1–4、modalityBridgeVideoTimeout默认 120000 ms。2.3 PII Maskerpriority 10pre post 双阶段这是唯一“永远不拦截”的内容类 guardrail只做标注meta.detections、meta.redacted或改写preCall克隆 payload遍历system、messages、input、prompt含纯字符串项对字符串型content/text字段应用processPII()来自 inputSanitizer.ts。当PII_REDACTION_ENABLEDtrue时出站 payload 中检出的 PII 会被直接脱敏——该行为与INPUT_SANITIZER_MODE完全独立后者只管注入策略未开启脱敏时只记录检测计数不改写内容。postCall深克隆响应运行sanitizePIIResponse()外加 Responses API 形态的 maskermaskResponsesOutput覆盖output_text与output[].content[].text。只要发生任何改写modifiedResponse就替换原响应继续流向后续 guardrail。2.4 Prompt Injection Guardpriority 20仅 preCall这是拦截型 guardrail由环境变量与构造选项共同驱动。行为参数表SettingEnv varDefaultEffectEnabledINPUT_SANITIZER_ENABLEDtrue设为false时 guardrail 直接短路ModeINJECTION_GUARD_MODE/INPUT_SANITIZER_MODEwarn注入策略block、warn或logredact为兼容值被接受但不删除注入文本请求侧 PII 改写由PII_REDACTION_ENABLED控制Block thresholdblockThreshold选项 /INPUT_SANITIZER_BLOCK_THRESHOLD别名INJECTION_GUARD_BLOCK_THRESHOLDhighblock模式下触发拦截所需的最低严重度默认时 medium 仅观测模式优先级可以在 promptInjection.ts 的getMode()中逐行印证let dbOverride: string | undefined; try { dbOverride getFeatureFlagOverride(INJECTION_GUARD_MODE); } catch { dbOverride undefined; } return (options.mode || dbOverride || process.env.INJECTION_GUARD_MODE || process.env.INPUT_SANITIZER_MODE || warn) as block | warn | log;即调用方options.mode→DB feature-flag 覆盖Dashboard → Settings → Feature Flags→ envINJECTION_GUARD_MODE→ envINPUT_SANITIZER_MODE→warn。Dashboard 覆盖优先于环境变量因此 Feature Flags UI 可以免重启实时切换运行中的 guardDB 读取是 fail-safe 的——出错时回落到纯 env 行为未设置覆盖时与仅 env 解析完全一致。检测来源有三处promptInjection.ts 的evaluatePromptInjection()sanitizeRequest()inputSanitizer.tspipeline 其他位置共用的检测器集合内置DEFAULT_GUARD_PATTERNSpromptInjection.tsconst DEFAULT_GUARD_PATTERNS: PatternLike[] [ { name: system_override_inline, pattern: /\bsystem\s*:\s*override\b/i, severity: high }, { name: markdown_system_block, pattern: /\s*system\b/i, severity: high }, ];构造选项传入的customPatterns字符串、RegExp 或{ name, pattern, severity }记录默认 severityhigh与内置检测按pattern:match:severity键去重合并。当mode block且至少一个检测达到阈值严重度时preCall返回{ block: true, message: Request rejected: suspicious content detected }并附meta.detections/meta.piiDetections计数warn/log模式只记日志、放行请求。warn模式还有一个细节无 high 级检测时连日志都不打。evaluatePromptInjection()作为共享 helper 导出供需要绕过 registry 直接评估 prompt 的调用方使用。扫描窗口限制v3.8.20 引入检测器只看拼接后 prompt 文本的前 16 KB——inputSanitizer.ts 中定义export const MAX_INJECTION_SCAN_BYTES 16 * 1024;detectInjection()与evaluatePromptInjection()都会先slice(0, MAX_INJECTION_SCAN_BYTES)再跑模式循环。注入指令通常位于输入前部因此该窗口在数百 KB 级 payload 上约束了正则的 CPU/GC 开销而不削弱检测能力。窗口行为由 injection-scan-window.test.ts 固化。2.5 Credential Maskerpriority 95pre post默认链最后一位它把“贴进 prompt或被工具结果回显的凭据”在上游往返两端都挡掉。实现要点docs/security/GUARDRAILS.md Credential Masker 一节纯 opt-insettings.credentialRedactionEnabled true或CREDENTIAL_REDACTION_ENABLEDtrue才生效关闭时是 no-op永不拦截、永不改写redactCredentials()用walkValue()遍历完整 payload/响应树防原型污染、WeakSet防环把命中替换为[REDACTED:type]占位符只克隆真正发生变化的分支CREDENTIAL_PATTERNS覆盖面LLM provider 密钥OpenAI、OpenAI-proj、Anthropic、Google、Hugging Face、Replicate、VCS/SaaS tokenGitHub、Slack、Linear、Notion、npm、Postman、Discord、支付密钥Stripe、Square、云密钥AWS access key、Twilio、SendGrid、Mailgun、私钥/JWT、带凭据的连接串mongodb://user:pass...等以及通用Authorization/x-api-key/api-key/apikey头值模式——头形 key 按结构脱敏仅值、保留Bearer/Basic前缀不走通用文本正则只改写并标注meta.credentialsRedacted、meta.count回归守卫为 credential-masker-guardrail.test.ts。三、基础契约BaseGuardrail与结果协议base.ts 给出的契约与 GUARDRAILS.md 的 Base Contract 一节一致class BaseGuardrail { enabled: boolean; name: string; priority: number; constructor(name: string, options?: { enabled?: boolean; priority?: number }); async preCall(payload: unknown, context: GuardrailContext): PromiseGuardrailResult | void; async postCall(response: unknown, context: GuardrailContext): PromiseGuardrailResult | void; } interface GuardrailResultTValue unknown { block?: boolean; // true 短路链条 message?: string; // 拦截时对外展示 meta?: Recordstring, unknown | null; modifiedPayload?: TValue; // preCall 返回以改写请求 modifiedResponse?: TValue; // postCall 返回以改写响应 } interface GuardrailContext { apiKeyInfo?: Recordstring, unknown | null; disabledGuardrails?: string[] | null; endpoint?: string | null; headers?: Headers | Recordstring, unknown | null; log?: GuardrailLog | Console | null; method?: string | null; model?: string | null; provider?: string | null; signal?: AbortSignal; sourceFormat?: string | null; stream?: boolean; targetFormat?: string | null; }几个容易踩坑的语义源码里都有明确处理“无变更”有三种等价写法返回void、{}或{ block: false }。registry 用一个asGuardrailResult()辅助函数把void/对象统一收敛为可判空的普通值registry.ts注释里专门解释了为什么必须先把返回值经unknown漏斗一次才能对GuardrailResult | void做result?.block判断——该契约由 guardrails-void-no-change-contract.test.ts 锁定返回modifiedPayload/modifiedResponse会替换链条中流动的当前值供后续 guardrail 消费PII 与凭据 masker 正是依赖这一点在桥接产出的描述文本上继续脱敏signal?: AbortSignal把调用方生命周期带入 guardrail请求 abort 是 fail-open 的唯一有意例外registry 的catch分支检测到context.signal?.aborted时会直接抛出Guardrail processing aborted媒体桥会停止工作并清理而不会把原始媒体恢复到已知不支持它的目标。GuardrailExecutionResultbase.ts记录每个 guardrail 的blocked、skipped、modified、error、meta与stagepre/post是 tracing 与审计的基本单元。四、Registry注册、执行与按请求禁用单例guardrailRegistry暴露的 APIregistry.tsregister(guardrail)—— 要求实例必须instanceof BaseGuardrail否则抛错按归一化名称替换同名旧 guardrail并按priority升序重排clear()/list()—— 管理辅助runPreCallHooks(payload, context)/runPostCallHooks(response, context)—— 按激活顺序迭代把 payload 串过modifiedPayload遇第一个block: true即停。两者都返回{ blocked, payload|response, results, guardrail?, message? }resetGuardrailsForTests({ registerDefaults })—— 清空状态并可选重注册默认 guardrail用于测试隔离registry.ts。按请求禁用 guardrailresolveDisabledGuardrails({ apiKeyInfo, body, headers })registry.ts聚合当前请求应跳过的 guardrail 名单。来源全部可选、全部合并去重apiKeyInfo.disabledGuardrailsAPI key 级别请求体顶层disabledGuardrails请求体metadata.disabledGuardrails请求头x-omniroute-disabled-guardrails或 legacyx-disabled-guardrails值可以是字符串数组也可以是逗号分隔字符串名称统一归一化为 lowercase kebab-casepii_masker→pii-masker见normalizeGuardrailName()。结果经context.disabledGuardrails传入 registry命中的 guardrail 被跳过并在results中标记skipped: true——这在 chat.ts 的调用点可以直接看到const preCallGuardrails await guardrailRegistry.runPreCallHooks(body, { apiKeyInfo: apiKeyInfo as any, disabledGuardrails: resolveDisabledGuardrails({ apiKeyInfo, body, headers: request.headers }), endpoint: new URL(request.url).pathname, headers: request.headers, log, method: request.method, model: modelStr, signal: request.signal, stream: body?.stream true, }); if (preCallGuardrails.blocked) { return errorResponse(HTTP_STATUS.BAD_REQUEST, preCallGuardrails.message || Request rejected: suspicious content detected); }五、每个请求的实际执行顺序对流经 src/sse/handlers/chat.ts 与 open-sse/handlers/chatCore.ts 的请求完整流程是resolveDisabledGuardrails(...)从 API key、body、headers 构建跳过列表guardrailRegistry.runPreCallHooks(body, ctx)按 priority 升序执行 pre 阶段被禁用的记skipped每个 guardrail 可用modifiedPayload改写 payload第一个block: true短路链条handler 返回 guardrail 拒绝响应如上文 400可能被改写的payload 进入 combo 路由与上游 dispatch注意 chat.ts 在 guardrail 之后还有RoutingModelOps.reconcileGuardrailReroute()对 vision bridge 的模型改写做对账见 chat.ts响应组装完成后runPostCallHooks(...)对响应跑同一链条响应侧的block: true会丢弃上游响应。抛异常的 guardrail 以error: message记录并经logger.warn上报链条继续——fail-open 是设计前提而非缺陷。六、配置矩阵内置 guardrail 读取的环境变量VariableUsed byEffectINPUT_SANITIZER_ENABLEDprompt-injectionfalse时完全关闭检测INPUT_SANITIZER_MODEprompt-injection注入策略warn/block/loglegacy 值redact不改写注入文本INJECTION_GUARD_MODEprompt-injection注入 guard 模式同时是 DB feature flag覆盖envDB ENVINPUT_SANITIZER_BLOCK_THRESHOLDprompt-injectionMODEblock拒收的最低严重度high默认/medium/lowINJECTION_GUARD_BLOCK_THRESHOLDprompt-injectionINPUT_SANITIZER_BLOCK_THRESHOLD的 legacy 别名PII_REDACTION_ENABLEDpii-maskertrue时请求 PII 被脱敏与注入模式相互独立PII_RESPONSE_SANITIZATION/_MODEpii-masker下游侧控制响应侧 masker 行为CREDENTIAL_REDACTION_ENABLEDcredential-maskeropt-in 开关等价于settings.credentialRedactionEnabledtrue模态桥Vision/Audio/Video不走 env而是读DB 支撑的设置 storegetSettings()Vision 主键modalityBridgeVisionEnabled、modalityBridgeVisionMode、modalityBridgeVisionModel、modalityBridgeVisionTaskAware、modalityBridgeVisionPrompt、modalityBridgeVisionTimeout、modalityBridgeVisionMaxImages、modalityBridgeVisionMaxChars缓存三件套modalityBridgeCacheEnabled默认true、modalityBridgeCacheTtlMinutes默认 601–1440、modalityBridgeCacheMaxEntries默认 20010–5000AudiomodalityBridgeAudioEnabled/Model/Timeout/MaxClips无 legacy 键回退VideomodalityBridgeVideoEnabled默认false/AnalysisMode/Model/FrameCount/SamplingPolicy/MaxVideos/Timeout。这些键在 settingsSchemas.ts 的updateSettingsSchema中做 Zod 校验迁移141_modality_bridge_settings.sql幂等地把旧visionBridge*值拷到新键绝不覆盖运营者已设置的modalityBridge*值。Dashboard 配置页为/dashboard/settings/modality-bridgeVision/Audio/Video 三个 URL 可寻址 Tab统计端点GET /api/modality-bridge/stats管理鉴权返回每模态的{ attempts, successes, bridged, cacheHits, failures, totalLatencyMs, ... }内存计数进程重启即清零遥测而非账务。完整的 env 参考见 docs/reference/ENVIRONMENT.md与之正交的韧性层熔断、冷却见 docs/architecture/RESILIENCE_GUIDE.md。七、编写自定义 Guardrail官方示例可直接照抄的骨架import { BaseGuardrail, guardrailRegistry } from /lib/guardrails; class BudgetGuardrail extends BaseGuardrail { constructor() { super(budget, { priority: 50 }); } async preCall(payload, ctx) { if (ctx.apiKeyInfo?.budgetExceeded) { return { block: true, message: Daily budget exceeded }; } return { block: false }; } } guardrailRegistry.register(new BudgetGuardrail());落地步骤在 src/lib/guardrails/ 下新建myGuardrail.ts继承BaseGuardrail实现preCall和/或postCall需要配置注入如deps.getSettings、deps.callVisionModel时仿照 Vision Bridge 的构造选项模式测试即可在无 DB/无网络下打满流程在导入时注册push 进registerDefaultGuardrails或运行时guardrailRegistry.register(...)——同名归一化后旧 guardrail 会被替换在 tests/unit/ 下补测试可参考现成样例guardrails-registry.test.ts、prompt-injection-guard.test.ts、tests/unit/guardrails/visionBridge.test.ts。测试惯例用例之间用resetGuardrailsForTests()重置到已知状态传{ registerDefaults: false }可以从空 registry 出发只注册被测 guardrail。八、Injection Guard 路由覆盖与 Red-Team 夜检除了 guardrail 注册表还有一层路由级createInjectionGuard/withInjectionGuard助手覆盖所有接收用户 prompt 的/v1路由同样遵守INJECTION_GUARD_MODE默认warn 只记日志block 返回 HTTP 400SECURITY_001TypeRoutesDefault modeText既有/v1/chat/completions、/v1/completions、/v1/relay/chat/completionswarnGenerative/v1/messages、/v1/responses、/v1/images/generations、/v1/images/edits、/v1/videos/generations、/v1/music/generations、/v1/audio/speechwarnData/v1/embeddings、/v1/rerank、/v1/search、/v1/moderationswarn文本抽取由 inputSanitizer.ts 的extractMessageContents()完成覆盖messages/input/prompt/querydocuments/instructions/system路由覆盖本身由 injection-route-coverage.test.ts 守卫。Red-team 夜检.github/workflows/nightly-llm-security.ymlcron 手动 dispatch文件在当前仓库存在有两个 jobpromptfoo-guard阻断性以INJECTION_GUARD_MODEblock运行promptfoo eval -c promptfooconfig.yaml配置文件 promptfooconfig.yaml 在仓库根目录。每条对抗样本ignore all previous instructions…、DAN 风格越狱等都断言响应携带error.code SECURITY_001即 guard 确实拒掉了请求garak建议性对本地 OmniRoute 实例http://localhost:20128/v1跑 garak--probes promptinject,dan,leakreplay。受 provider secretPROMPTFOO_PROVIDER_KEY门控缺 secret 时优雅跳过且以|| true收尾只报告不 fail CI。moderations路由为了一致性也被纳入覆盖——block 模式运营者可通过resolveDisabledGuardrails豁免它。九、要点回顾链条语义pre 阶段改写请求、post 阶段改写响应block: true是唯一合法的拦截方式异常一律 fail-openabort 除外优先级编排数字小的先跑同名注册即替换results数组skipped/modified/error/meta提供逐 guardrail 的可观测性注入防护模式 DB 覆盖 INJECTION_GUARD_MODEINPUT_SANITIZER_MODEwarnblock 需达blockThreshold默认 high扫描只看前 16 KBMAX_INJECTION_SCAN_BYTES脱敏分工PII 看PII_REDACTION_ENABLED凭据看CREDENTIAL_REDACTION_ENABLED与注入模式互相独立多模态桥接Vision/Audio/Video 三桥读 DB 设置而非 env且都带“失败保留原件或显式占位”的契约保证原始媒体绝不静默泄漏到 text-only 后端。想继续深入建议按此顺序阅读源码base.ts → registry.ts → promptInjection.ts → inputSanitizer.ts → chat.ts 的 guardrail 调用点再配合 tests/unit/guardrails/ 下的行为测试交叉验证。【免费下载链接】OmniRouteNever stop coding. Free MIT AI gateway: one endpoint, 352 providers (150 free), 1200 models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline Copilot. Quota-aware auto-fallback, RTKCaveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550 contributors项目地址: https://gitcode.com/GitHub_Trending/om/OmniRoute创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考