@silmaril-security/sdk 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +8 -6
- package/dist/adapters/langchain.cjs +4 -1
- package/dist/adapters/langchain.cjs.map +1 -1
- package/dist/adapters/langchain.d.cts +1 -1
- package/dist/adapters/langchain.d.ts +1 -1
- package/dist/adapters/langchain.js +4 -1
- package/dist/adapters/langchain.js.map +1 -1
- package/dist/adapters/vercel.cjs +4 -1
- package/dist/adapters/vercel.cjs.map +1 -1
- package/dist/adapters/vercel.d.cts +1 -1
- package/dist/adapters/vercel.d.ts +1 -1
- package/dist/adapters/vercel.js +4 -1
- package/dist/adapters/vercel.js.map +1 -1
- package/dist/{firewall-C0ibVbst.d.cts → firewall-Cdnk_UWY.d.cts} +20 -1
- package/dist/{firewall-C0ibVbst.d.ts → firewall-Cdnk_UWY.d.ts} +20 -1
- package/dist/index.cjs +53 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +53 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exceptions.ts","../../src/hooks.ts","../../src/adapters/vercel.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { HookLabel } from \"./hooks.js\";\nimport type { BlockResult } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n }): string {\n const truncated =\n params.promptText.length > MAX_PROMPT_DISPLAY_LEN\n ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...`\n : params.promptText;\n return (\n `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\nexport class SilmarilApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly body: string;\n readonly details: MalformedInputDetails | undefined;\n readonly error: string | undefined;\n readonly apiMessage: string | undefined;\n\n constructor(params: {\n status: number;\n statusText: string;\n body: string;\n details?: MalformedInputDetails;\n error?: string;\n apiMessage?: string;\n }) {\n const statusText = params.statusText ? ` ${params.statusText}` : \"\";\n super(`Silmaril API error ${params.status}${statusText}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n const parsed = parseErrorBody(params.body);\n this.details = params.details ?? parsed.details;\n this.error = params.error ?? parsed.error;\n this.apiMessage = params.apiMessage ?? parsed.apiMessage;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\n }\n}\n\nfunction parseErrorBody(body: string): {\n details: MalformedInputDetails | undefined;\n error: string | undefined;\n apiMessage: string | undefined;\n} {\n try {\n const parsed = JSON.parse(body) as unknown;\n if (!parsed || typeof parsed !== \"object\") {\n return { details: undefined, error: undefined, apiMessage: undefined };\n }\n const data = parsed as Record<string, unknown>;\n return {\n details:\n data.details && typeof data.details === \"object\"\n ? (data.details as MalformedInputDetails)\n : undefined,\n error: typeof data.error === \"string\" ? data.error : undefined,\n apiMessage: typeof data.message === \"string\" ? data.message : undefined,\n };\n } catch {\n return { details: undefined, error: undefined, apiMessage: undefined };\n }\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nexport const HookLabel = {\n USER_INPUT: \"user_input\",\n SYSTEM_PROMPT: \"system_prompt\",\n TOOL_CALL: \"tool_call\",\n TOOL_RESPONSE: \"tool_response\",\n LLM_OUTPUT: \"llm_output\",\n UNKNOWN: \"unknown\",\n} as const;\n\nexport type HookLabel = (typeof HookLabel)[keyof typeof HookLabel];\n\nexport const FirewallHook = {\n LLM_START: \"on_llm_start\",\n CHAT_MODEL_START: \"on_chat_model_start\",\n TOOL_START: \"on_tool_start\",\n RETRIEVER_START: \"on_retriever_start\",\n LLM_END: \"on_llm_end\",\n TOOL_END: \"on_tool_end\",\n RETRIEVER_END: \"on_retriever_end\",\n} as const;\n\nexport type FirewallHook = (typeof FirewallHook)[keyof typeof FirewallHook];\n\nexport const DEFAULT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n]);\n\nexport const INPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.RETRIEVER_START,\n]);\n\nexport const OUTPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_END,\n FirewallHook.TOOL_END,\n FirewallHook.RETRIEVER_END,\n]);\n\nexport const ALL_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n ...INPUT_HOOKS,\n ...OUTPUT_HOOKS,\n]);\n\nexport const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>> = {\n [FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,\n [FirewallHook.LLM_START]: HookLabel.USER_INPUT,\n [FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,\n [FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,\n [FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT,\n};\n\nconst FIREWALL_HOOK_VALUES: ReadonlySet<string> = new Set(Object.values(FirewallHook));\n\nexport function resolveHooks(\n hooks: Iterable<FirewallHook | string> | undefined,\n): ReadonlySet<FirewallHook> {\n if (hooks === undefined) {\n return DEFAULT_HOOKS;\n }\n const resolved = new Set<FirewallHook>();\n for (const h of hooks) {\n if (!FIREWALL_HOOK_VALUES.has(h)) {\n throw new Error(`Invalid FirewallHook value: ${String(h)}`);\n }\n resolved.add(h as FirewallHook);\n }\n return resolved;\n}\n\nexport function prependHook(text: string, hook: HookLabel | string | undefined | null): string {\n if (hook === undefined || hook === null) {\n return text;\n }\n if (hook === HookLabel.UNKNOWN) {\n return text;\n }\n return `[HOOK:${hook}] ${text}`;\n}\n\nexport function prependToolName(text: string, toolName: string | undefined | null): string {\n if (toolName === undefined || toolName === null) {\n return text;\n }\n return `[TOOL:${toolName}] ${text}`;\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { FirewallBlockedException } from \"../exceptions.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { BlockResult, MiddlewareOptions } from \"../types.js\";\n\ninterface VercelContentPart {\n type?: string;\n text?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n output?: unknown;\n result?: unknown;\n}\n\ninterface VercelPromptMessage {\n role: string;\n content: string | ReadonlyArray<VercelContentPart | string>;\n}\n\ninterface VercelToolCall {\n toolCallType?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n input?: unknown;\n}\n\ninterface VercelGenerateResult {\n text?: string;\n toolCalls?: ReadonlyArray<VercelToolCall>;\n}\n\ninterface WrapGenerateArgs<TResult extends VercelGenerateResult & Record<string, unknown>> {\n params: { prompt?: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doGenerate: () => PromiseLike<TResult>;\n}\n\ninterface WrapStreamArgs<\n TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>,\n> {\n params: { prompt?: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doStream: () => PromiseLike<TResult>;\n}\n\ninterface StreamPart {\n type?: string;\n textDelta?: string;\n delta?: string;\n}\n\ninterface VercelStepContext {\n toolName: string | undefined;\n toolCallId: string | undefined;\n}\n\nfunction stringifyToolValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n if (typeof value === \"string\") {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction extractContentText(content: VercelPromptMessage[\"content\"]): string {\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction iterateToolResultParts(\n message: VercelPromptMessage,\n): Array<{ text: string; toolName: string | undefined; toolCallId: string | undefined }> {\n if (typeof message.content === \"string\") {\n return [];\n }\n const out: Array<{ text: string; toolName: string | undefined; toolCallId: string | undefined }> = [];\n for (const part of message.content) {\n if (typeof part === \"string\") {\n continue;\n }\n if (part.type === \"tool-result\") {\n const text = stringifyToolResult(part.result !== undefined ? part.result : part.output);\n if (text.trim()) {\n out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });\n }\n }\n }\n return out;\n}\n\nfunction stringifyToolResult(value: unknown): string {\n if (!isRecord(value)) {\n return stringifyToolValue(value);\n }\n if ((value.type === \"text\" || value.type === \"error-text\") && typeof value.value === \"string\") {\n return value.value;\n }\n if ((value.type === \"json\" || value.type === \"error-json\") && value.value !== undefined) {\n return stringifyToolValue(value.value);\n }\n if (value.type === \"content\" && Array.isArray(value.value)) {\n return value.value\n .map((part) => (isRecord(part) && part.type === \"text\" && typeof part.text === \"string\" ? part.text : \"\"))\n .filter((text) => text.length > 0)\n .join(\" \");\n }\n return stringifyToolValue(value);\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<VercelPromptMessage>,\n): VercelPromptMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (msg.role.toLowerCase() === \"user\") {\n return msg;\n }\n }\n return undefined;\n}\n\nexport interface FirewallMiddleware {\n readonly specificationVersion: \"v3\";\n readonly middlewareVersion: \"v2\";\n wrapGenerate: <TResult extends VercelGenerateResult & Record<string, unknown>>(\n args: WrapGenerateArgs<TResult>,\n ) => Promise<TResult>;\n wrapStream: <TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>(\n args: WrapStreamArgs<TResult>,\n ) => Promise<TResult>;\n}\n\nexport function createMiddleware(\n firewall: Firewall,\n options: MiddlewareOptions = {},\n): FirewallMiddleware {\n const scanInput = options.scanInput ?? true;\n const scanOutput = options.scanOutput ?? false;\n const requestedMode = options.mode ?? (\n options.shadowMode === undefined\n ? firewall.mode\n : options.shadowMode ? \"shadow\" : \"block\"\n );\n\n const classifyOrBlock = async (\n text: string,\n hook: HookLabel,\n context: VercelStepContext = { toolName: undefined, toolCallId: undefined },\n ): Promise<void> => {\n if (!text.trim()) {\n return;\n }\n const { toolName, toolCallId } = context;\n const result: BlockResult = await firewall.classify(\n text,\n {\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(requestedMode !== undefined ? { mode: requestedMode } : {}),\n },\n );\n const threshold = result.threshold;\n const blocked = result.prediction === \"MALICIOUS\";\n const effectiveMode = requestedMode ?? result.mode ?? \"block\";\n const commonEventFields = {\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(toolCallId !== undefined ? { toolCallId } : {}),\n text,\n result,\n };\n options.onClassify?.({\n ...commonEventFields,\n blocked,\n mode: effectiveMode,\n shadowMode: effectiveMode === \"shadow\",\n });\n if (!blocked || effectiveMode !== \"block\") {\n return;\n }\n\n const err = new FirewallBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(toolCallId !== undefined ? { toolCallId } : {}),\n result,\n });\n options.onBlocked?.(err);\n throw err;\n };\n\n const scanPrompt = async (prompt: ReadonlyArray<VercelPromptMessage>): Promise<void> => {\n if (prompt.length === 0) {\n return;\n }\n const last = prompt[prompt.length - 1]!;\n const role = last.role.toLowerCase();\n\n // If the newest message is a tool result (typical mid-flow in a\n // tool-calling loop), classify each tool-result part individually with\n // tool_response + toolName. No manual wiring from the caller — the\n // toolName is read directly from the Vercel content part.\n if (role === \"tool\") {\n for (const { text, toolName, toolCallId } of iterateToolResultParts(last)) {\n await classifyOrBlock(text, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });\n }\n return;\n }\n\n // Otherwise, fall back to scanning the most recent user message.\n // Covers: [user], [system, user], [user, assistant(text), user], etc.\n const lastUser = findLastUserMessage(prompt);\n if (!lastUser) {\n return;\n }\n const text = extractContentText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classifyOrBlock(text, HookLabel.USER_INPUT);\n };\n\n const scanGenerateResult = async (result: VercelGenerateResult): Promise<void> => {\n if (scanOutput && typeof result.text === \"string\" && result.text.length > 0) {\n await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);\n }\n if (options.scanToolCalls && Array.isArray(result.toolCalls)) {\n for (const call of result.toolCalls) {\n if (!call || typeof call !== \"object\") {\n continue;\n }\n const toolInput = call.args !== undefined ? call.args : call.input;\n const args =\n typeof toolInput === \"string\" ? toolInput : stringifyToolValue(toolInput);\n const toolName = typeof call.toolName === \"string\" ? call.toolName : undefined;\n const toolCallId = typeof call.toolCallId === \"string\" ? call.toolCallId : undefined;\n if (args.trim()) {\n await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });\n }\n }\n }\n };\n\n return {\n specificationVersion: \"v3\",\n middlewareVersion: \"v2\",\n\n async wrapGenerate<TResult extends VercelGenerateResult & Record<string, unknown>>({\n params,\n doGenerate,\n }: WrapGenerateArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt ?? []);\n }\n const result = await doGenerate();\n await scanGenerateResult(result);\n return result;\n },\n\n async wrapStream<TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>({\n params,\n doStream,\n }: WrapStreamArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt ?? []);\n }\n const { stream, ...rest } = await doStream();\n if (!scanOutput) {\n return { stream, ...rest } as unknown as TResult;\n }\n\n let buffered = \"\";\n const transformed = stream.pipeThrough(\n new TransformStream<unknown, unknown>({\n transform(chunk, controller): void {\n const part = chunk as StreamPart;\n if (part && part.type === \"text-delta\") {\n const delta = part.textDelta ?? part.delta ?? \"\";\n buffered += delta;\n }\n controller.enqueue(chunk);\n },\n async flush(controller): Promise<void> {\n if (!buffered.trim()) {\n return;\n }\n try {\n await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);\n } catch (err) {\n if (err instanceof FirewallBlockedException) {\n controller.enqueue({ type: \"error\", error: err });\n return;\n }\n throw err;\n }\n },\n }),\n );\n return { stream: transformed, ...rest } as unknown as TResult;\n },\n };\n}\n"],"mappings":"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","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../../src/exceptions.ts","../../src/hooks.ts","../../src/adapters/vercel.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { HookLabel } from \"./hooks.js\";\nimport type { BlockResult } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n result?: BlockResult;\n }): string {\n const truncated =\n params.promptText.length > MAX_PROMPT_DISPLAY_LEN\n ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...`\n : params.promptText;\n if (\n params.result?.prediction !== \"MALICIOUS\" &&\n params.result?.governance?.action === \"block\"\n ) {\n return `Request blocked by Silmaril Firewall governance policy: '${truncated}'`;\n }\n return (\n `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\nexport class SilmarilApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly body: string;\n readonly details: MalformedInputDetails | undefined;\n readonly error: string | undefined;\n readonly apiMessage: string | undefined;\n\n constructor(params: {\n status: number;\n statusText: string;\n body: string;\n details?: MalformedInputDetails;\n error?: string;\n apiMessage?: string;\n }) {\n const statusText = params.statusText ? ` ${params.statusText}` : \"\";\n super(`Silmaril API error ${params.status}${statusText}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n const parsed = parseErrorBody(params.body);\n this.details = params.details ?? parsed.details;\n this.error = params.error ?? parsed.error;\n this.apiMessage = params.apiMessage ?? parsed.apiMessage;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\n }\n}\n\nfunction parseErrorBody(body: string): {\n details: MalformedInputDetails | undefined;\n error: string | undefined;\n apiMessage: string | undefined;\n} {\n try {\n const parsed = JSON.parse(body) as unknown;\n if (!parsed || typeof parsed !== \"object\") {\n return { details: undefined, error: undefined, apiMessage: undefined };\n }\n const data = parsed as Record<string, unknown>;\n return {\n details:\n data.details && typeof data.details === \"object\"\n ? (data.details as MalformedInputDetails)\n : undefined,\n error: typeof data.error === \"string\" ? data.error : undefined,\n apiMessage: typeof data.message === \"string\" ? data.message : undefined,\n };\n } catch {\n return { details: undefined, error: undefined, apiMessage: undefined };\n }\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nexport const HookLabel = {\n USER_INPUT: \"user_input\",\n SYSTEM_PROMPT: \"system_prompt\",\n TOOL_CALL: \"tool_call\",\n TOOL_RESPONSE: \"tool_response\",\n LLM_OUTPUT: \"llm_output\",\n UNKNOWN: \"unknown\",\n} as const;\n\nexport type HookLabel = (typeof HookLabel)[keyof typeof HookLabel];\n\nexport const FirewallHook = {\n LLM_START: \"on_llm_start\",\n CHAT_MODEL_START: \"on_chat_model_start\",\n TOOL_START: \"on_tool_start\",\n RETRIEVER_START: \"on_retriever_start\",\n LLM_END: \"on_llm_end\",\n TOOL_END: \"on_tool_end\",\n RETRIEVER_END: \"on_retriever_end\",\n} as const;\n\nexport type FirewallHook = (typeof FirewallHook)[keyof typeof FirewallHook];\n\nexport const DEFAULT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n]);\n\nexport const INPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.RETRIEVER_START,\n]);\n\nexport const OUTPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_END,\n FirewallHook.TOOL_END,\n FirewallHook.RETRIEVER_END,\n]);\n\nexport const ALL_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n ...INPUT_HOOKS,\n ...OUTPUT_HOOKS,\n]);\n\nexport const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>> = {\n [FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,\n [FirewallHook.LLM_START]: HookLabel.USER_INPUT,\n [FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,\n [FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,\n [FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT,\n};\n\nconst FIREWALL_HOOK_VALUES: ReadonlySet<string> = new Set(Object.values(FirewallHook));\n\nexport function resolveHooks(\n hooks: Iterable<FirewallHook | string> | undefined,\n): ReadonlySet<FirewallHook> {\n if (hooks === undefined) {\n return DEFAULT_HOOKS;\n }\n const resolved = new Set<FirewallHook>();\n for (const h of hooks) {\n if (!FIREWALL_HOOK_VALUES.has(h)) {\n throw new Error(`Invalid FirewallHook value: ${String(h)}`);\n }\n resolved.add(h as FirewallHook);\n }\n return resolved;\n}\n\nexport function prependHook(text: string, hook: HookLabel | string | undefined | null): string {\n if (hook === undefined || hook === null) {\n return text;\n }\n if (hook === HookLabel.UNKNOWN) {\n return text;\n }\n return `[HOOK:${hook}] ${text}`;\n}\n\nexport function prependToolName(text: string, toolName: string | undefined | null): string {\n if (toolName === undefined || toolName === null) {\n return text;\n }\n return `[TOOL:${toolName}] ${text}`;\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { FirewallBlockedException } from \"../exceptions.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { BlockResult, MiddlewareOptions } from \"../types.js\";\n\ninterface VercelContentPart {\n type?: string;\n text?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n output?: unknown;\n result?: unknown;\n}\n\ninterface VercelPromptMessage {\n role: string;\n content: string | ReadonlyArray<VercelContentPart | string>;\n}\n\ninterface VercelToolCall {\n toolCallType?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n input?: unknown;\n}\n\ninterface VercelGenerateResult {\n text?: string;\n toolCalls?: ReadonlyArray<VercelToolCall>;\n}\n\ninterface WrapGenerateArgs<TResult extends VercelGenerateResult & Record<string, unknown>> {\n params: { prompt?: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doGenerate: () => PromiseLike<TResult>;\n}\n\ninterface WrapStreamArgs<\n TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>,\n> {\n params: { prompt?: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doStream: () => PromiseLike<TResult>;\n}\n\ninterface StreamPart {\n type?: string;\n textDelta?: string;\n delta?: string;\n}\n\ninterface VercelStepContext {\n toolName: string | undefined;\n toolCallId: string | undefined;\n}\n\nfunction stringifyToolValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n if (typeof value === \"string\") {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction extractContentText(content: VercelPromptMessage[\"content\"]): string {\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction iterateToolResultParts(\n message: VercelPromptMessage,\n): Array<{ text: string; toolName: string | undefined; toolCallId: string | undefined }> {\n if (typeof message.content === \"string\") {\n return [];\n }\n const out: Array<{ text: string; toolName: string | undefined; toolCallId: string | undefined }> = [];\n for (const part of message.content) {\n if (typeof part === \"string\") {\n continue;\n }\n if (part.type === \"tool-result\") {\n const text = stringifyToolResult(part.result !== undefined ? part.result : part.output);\n if (text.trim()) {\n out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });\n }\n }\n }\n return out;\n}\n\nfunction stringifyToolResult(value: unknown): string {\n if (!isRecord(value)) {\n return stringifyToolValue(value);\n }\n if ((value.type === \"text\" || value.type === \"error-text\") && typeof value.value === \"string\") {\n return value.value;\n }\n if ((value.type === \"json\" || value.type === \"error-json\") && value.value !== undefined) {\n return stringifyToolValue(value.value);\n }\n if (value.type === \"content\" && Array.isArray(value.value)) {\n return value.value\n .map((part) => (isRecord(part) && part.type === \"text\" && typeof part.text === \"string\" ? part.text : \"\"))\n .filter((text) => text.length > 0)\n .join(\" \");\n }\n return stringifyToolValue(value);\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<VercelPromptMessage>,\n): VercelPromptMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (msg.role.toLowerCase() === \"user\") {\n return msg;\n }\n }\n return undefined;\n}\n\nexport interface FirewallMiddleware {\n readonly specificationVersion: \"v3\";\n readonly middlewareVersion: \"v2\";\n wrapGenerate: <TResult extends VercelGenerateResult & Record<string, unknown>>(\n args: WrapGenerateArgs<TResult>,\n ) => Promise<TResult>;\n wrapStream: <TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>(\n args: WrapStreamArgs<TResult>,\n ) => Promise<TResult>;\n}\n\nexport function createMiddleware(\n firewall: Firewall,\n options: MiddlewareOptions = {},\n): FirewallMiddleware {\n const scanInput = options.scanInput ?? true;\n const scanOutput = options.scanOutput ?? false;\n const requestedMode = options.mode ?? (\n options.shadowMode === undefined\n ? firewall.mode\n : options.shadowMode ? \"shadow\" : \"block\"\n );\n\n const classifyOrBlock = async (\n text: string,\n hook: HookLabel,\n context: VercelStepContext = { toolName: undefined, toolCallId: undefined },\n ): Promise<void> => {\n if (!text.trim()) {\n return;\n }\n const { toolName, toolCallId } = context;\n const result: BlockResult = await firewall.classify(\n text,\n {\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(requestedMode !== undefined ? { mode: requestedMode } : {}),\n },\n );\n const threshold = result.threshold;\n const blocked = result.prediction === \"MALICIOUS\" || result.governance?.action === \"block\";\n const effectiveMode = requestedMode ?? result.mode ?? \"block\";\n const commonEventFields = {\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(toolCallId !== undefined ? { toolCallId } : {}),\n text,\n result,\n };\n options.onClassify?.({\n ...commonEventFields,\n blocked,\n mode: effectiveMode,\n shadowMode: effectiveMode === \"shadow\",\n });\n if (!blocked || effectiveMode !== \"block\") {\n return;\n }\n\n const err = new FirewallBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(toolCallId !== undefined ? { toolCallId } : {}),\n result,\n });\n options.onBlocked?.(err);\n throw err;\n };\n\n const scanPrompt = async (prompt: ReadonlyArray<VercelPromptMessage>): Promise<void> => {\n if (prompt.length === 0) {\n return;\n }\n const last = prompt[prompt.length - 1]!;\n const role = last.role.toLowerCase();\n\n // If the newest message is a tool result (typical mid-flow in a\n // tool-calling loop), classify each tool-result part individually with\n // tool_response + toolName. No manual wiring from the caller — the\n // toolName is read directly from the Vercel content part.\n if (role === \"tool\") {\n for (const { text, toolName, toolCallId } of iterateToolResultParts(last)) {\n await classifyOrBlock(text, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });\n }\n return;\n }\n\n // Otherwise, fall back to scanning the most recent user message.\n // Covers: [user], [system, user], [user, assistant(text), user], etc.\n const lastUser = findLastUserMessage(prompt);\n if (!lastUser) {\n return;\n }\n const text = extractContentText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classifyOrBlock(text, HookLabel.USER_INPUT);\n };\n\n const scanGenerateResult = async (result: VercelGenerateResult): Promise<void> => {\n if (scanOutput && typeof result.text === \"string\" && result.text.length > 0) {\n await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);\n }\n if (options.scanToolCalls && Array.isArray(result.toolCalls)) {\n for (const call of result.toolCalls) {\n if (!call || typeof call !== \"object\") {\n continue;\n }\n const toolInput = call.args !== undefined ? call.args : call.input;\n const args =\n typeof toolInput === \"string\" ? toolInput : stringifyToolValue(toolInput);\n const toolName = typeof call.toolName === \"string\" ? call.toolName : undefined;\n const toolCallId = typeof call.toolCallId === \"string\" ? call.toolCallId : undefined;\n if (args.trim()) {\n await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });\n }\n }\n }\n };\n\n return {\n specificationVersion: \"v3\",\n middlewareVersion: \"v2\",\n\n async wrapGenerate<TResult extends VercelGenerateResult & Record<string, unknown>>({\n params,\n doGenerate,\n }: WrapGenerateArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt ?? []);\n }\n const result = await doGenerate();\n await scanGenerateResult(result);\n return result;\n },\n\n async wrapStream<TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>({\n params,\n doStream,\n }: WrapStreamArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt ?? []);\n }\n const { stream, ...rest } = await doStream();\n if (!scanOutput) {\n return { stream, ...rest } as unknown as TResult;\n }\n\n let buffered = \"\";\n const transformed = stream.pipeThrough(\n new TransformStream<unknown, unknown>({\n transform(chunk, controller): void {\n const part = chunk as StreamPart;\n if (part && part.type === \"text-delta\") {\n const delta = part.textDelta ?? part.delta ?? \"\";\n buffered += delta;\n }\n controller.enqueue(chunk);\n },\n async flush(controller): Promise<void> {\n if (!buffered.trim()) {\n return;\n }\n try {\n await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);\n } catch (err) {\n if (err instanceof FirewallBlockedException) {\n controller.enqueue({ type: \"error\", error: err });\n return;\n }\n throw err;\n }\n },\n }),\n );\n return { stream: transformed, ...rest } as unknown as TResult;\n },\n };\n}\n"],"mappings":"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","names":["text"]}
|
|
@@ -72,6 +72,22 @@ declare function normalizeHarmfulOutcomeMap(values: unknown, fieldName: string):
|
|
|
72
72
|
|
|
73
73
|
type Prediction = "BENIGN" | "MALICIOUS";
|
|
74
74
|
type FirewallMode = "shadow" | "warn" | "block";
|
|
75
|
+
type GovernanceAction = "allow" | "block";
|
|
76
|
+
type GovernanceResourceKind = "agent" | "tool" | "mcp_server" | "mcp_tool" | "plugin" | "skill" | "extension";
|
|
77
|
+
interface GovernanceResource {
|
|
78
|
+
readonly kind: GovernanceResourceKind;
|
|
79
|
+
readonly id?: string;
|
|
80
|
+
readonly parentId?: string;
|
|
81
|
+
}
|
|
82
|
+
interface GovernanceContext {
|
|
83
|
+
readonly agent?: string;
|
|
84
|
+
readonly resource?: GovernanceResource;
|
|
85
|
+
}
|
|
86
|
+
interface GovernanceDecision {
|
|
87
|
+
readonly action: GovernanceAction;
|
|
88
|
+
readonly ruleId?: string;
|
|
89
|
+
readonly policyVersion: string;
|
|
90
|
+
}
|
|
75
91
|
interface BlockResult {
|
|
76
92
|
readonly prediction: Prediction;
|
|
77
93
|
readonly score: number;
|
|
@@ -82,6 +98,7 @@ interface BlockResult {
|
|
|
82
98
|
readonly outcomeScores?: Readonly<Partial<Record<HarmfulOutcome, number>>>;
|
|
83
99
|
readonly detectorScores?: Readonly<Partial<Record<HarmfulOutcome, number>>>;
|
|
84
100
|
readonly detectorCounts?: Readonly<Partial<Record<HarmfulOutcome, number>>>;
|
|
101
|
+
readonly governance?: GovernanceDecision;
|
|
85
102
|
}
|
|
86
103
|
interface FirewallOptions {
|
|
87
104
|
apiKey: string;
|
|
@@ -96,6 +113,7 @@ interface ClassifyOptions {
|
|
|
96
113
|
mode?: FirewallMode;
|
|
97
114
|
hook?: HookLabel;
|
|
98
115
|
toolName?: string;
|
|
116
|
+
governance?: GovernanceContext;
|
|
99
117
|
metadata?: ClassificationMetadata;
|
|
100
118
|
requestId?: string;
|
|
101
119
|
}
|
|
@@ -103,6 +121,7 @@ interface ClassifyBatchOptions {
|
|
|
103
121
|
mode?: FirewallMode;
|
|
104
122
|
hooks?: readonly HookLabel[];
|
|
105
123
|
toolNames?: readonly (string | undefined)[];
|
|
124
|
+
governance?: readonly (GovernanceContext | undefined)[];
|
|
106
125
|
metadata?: readonly (ClassificationMetadata | undefined)[];
|
|
107
126
|
requestId?: string;
|
|
108
127
|
}
|
|
@@ -216,4 +235,4 @@ declare class Firewall {
|
|
|
216
235
|
private classifySingle;
|
|
217
236
|
}
|
|
218
237
|
|
|
219
|
-
export { ALL_HOOKS as A, type BlockResult as B, type ClassificationMetadata as C, DEFAULT_HOOKS as D, FIREWALL_HOOK_TO_LABEL as F, HookLabel as H, INPUT_HOOKS as I, type KnownHarmfulOutcome as K, type LangChainAdapterOptions as L, type MiddlewareOptions as M, OUTCOME_DESCRIPTIONS as O, PRIMARY_OUTCOMES as P, type UnknownOutcome as U, type ClassifyBatchOptions as a, type ClassifyEvent as b, type ClassifyOptions as c, DEFAULT_TIMEOUT_MS as d, Firewall as e, FirewallHook as f, type FirewallMiddleware as g, type FirewallMode as h, type FirewallOptions as i,
|
|
238
|
+
export { ALL_HOOKS as A, type BlockResult as B, type ClassificationMetadata as C, DEFAULT_HOOKS as D, prependHook as E, FIREWALL_HOOK_TO_LABEL as F, type GovernanceAction as G, HookLabel as H, INPUT_HOOKS as I, prependToolName as J, type KnownHarmfulOutcome as K, type LangChainAdapterOptions as L, type MiddlewareOptions as M, resolveHooks as N, OUTCOME_DESCRIPTIONS as O, PRIMARY_OUTCOMES as P, createMiddleware as Q, type UnknownOutcome as U, type ClassifyBatchOptions as a, type ClassifyEvent as b, type ClassifyOptions as c, DEFAULT_TIMEOUT_MS as d, Firewall as e, FirewallHook as f, type FirewallMiddleware as g, type FirewallMode as h, type FirewallOptions as i, type GovernanceContext as j, type GovernanceDecision as k, type GovernanceResource as l, type GovernanceResourceKind as m, HARMFUL_OUTCOMES as n, type HarmfulOutcome as o, type KnownPrimaryOutcome as p, type LangChainFirewallHandler as q, OUTPUT_HOOKS as r, Outcome as s, type Prediction as t, type PrimaryOutcome as u, isHarmfulOutcome as v, isPrimaryOutcome as w, normalizeHarmfulOutcome as x, normalizeHarmfulOutcomeMap as y, normalizePrimaryOutcome as z };
|
|
@@ -72,6 +72,22 @@ declare function normalizeHarmfulOutcomeMap(values: unknown, fieldName: string):
|
|
|
72
72
|
|
|
73
73
|
type Prediction = "BENIGN" | "MALICIOUS";
|
|
74
74
|
type FirewallMode = "shadow" | "warn" | "block";
|
|
75
|
+
type GovernanceAction = "allow" | "block";
|
|
76
|
+
type GovernanceResourceKind = "agent" | "tool" | "mcp_server" | "mcp_tool" | "plugin" | "skill" | "extension";
|
|
77
|
+
interface GovernanceResource {
|
|
78
|
+
readonly kind: GovernanceResourceKind;
|
|
79
|
+
readonly id?: string;
|
|
80
|
+
readonly parentId?: string;
|
|
81
|
+
}
|
|
82
|
+
interface GovernanceContext {
|
|
83
|
+
readonly agent?: string;
|
|
84
|
+
readonly resource?: GovernanceResource;
|
|
85
|
+
}
|
|
86
|
+
interface GovernanceDecision {
|
|
87
|
+
readonly action: GovernanceAction;
|
|
88
|
+
readonly ruleId?: string;
|
|
89
|
+
readonly policyVersion: string;
|
|
90
|
+
}
|
|
75
91
|
interface BlockResult {
|
|
76
92
|
readonly prediction: Prediction;
|
|
77
93
|
readonly score: number;
|
|
@@ -82,6 +98,7 @@ interface BlockResult {
|
|
|
82
98
|
readonly outcomeScores?: Readonly<Partial<Record<HarmfulOutcome, number>>>;
|
|
83
99
|
readonly detectorScores?: Readonly<Partial<Record<HarmfulOutcome, number>>>;
|
|
84
100
|
readonly detectorCounts?: Readonly<Partial<Record<HarmfulOutcome, number>>>;
|
|
101
|
+
readonly governance?: GovernanceDecision;
|
|
85
102
|
}
|
|
86
103
|
interface FirewallOptions {
|
|
87
104
|
apiKey: string;
|
|
@@ -96,6 +113,7 @@ interface ClassifyOptions {
|
|
|
96
113
|
mode?: FirewallMode;
|
|
97
114
|
hook?: HookLabel;
|
|
98
115
|
toolName?: string;
|
|
116
|
+
governance?: GovernanceContext;
|
|
99
117
|
metadata?: ClassificationMetadata;
|
|
100
118
|
requestId?: string;
|
|
101
119
|
}
|
|
@@ -103,6 +121,7 @@ interface ClassifyBatchOptions {
|
|
|
103
121
|
mode?: FirewallMode;
|
|
104
122
|
hooks?: readonly HookLabel[];
|
|
105
123
|
toolNames?: readonly (string | undefined)[];
|
|
124
|
+
governance?: readonly (GovernanceContext | undefined)[];
|
|
106
125
|
metadata?: readonly (ClassificationMetadata | undefined)[];
|
|
107
126
|
requestId?: string;
|
|
108
127
|
}
|
|
@@ -216,4 +235,4 @@ declare class Firewall {
|
|
|
216
235
|
private classifySingle;
|
|
217
236
|
}
|
|
218
237
|
|
|
219
|
-
export { ALL_HOOKS as A, type BlockResult as B, type ClassificationMetadata as C, DEFAULT_HOOKS as D, FIREWALL_HOOK_TO_LABEL as F, HookLabel as H, INPUT_HOOKS as I, type KnownHarmfulOutcome as K, type LangChainAdapterOptions as L, type MiddlewareOptions as M, OUTCOME_DESCRIPTIONS as O, PRIMARY_OUTCOMES as P, type UnknownOutcome as U, type ClassifyBatchOptions as a, type ClassifyEvent as b, type ClassifyOptions as c, DEFAULT_TIMEOUT_MS as d, Firewall as e, FirewallHook as f, type FirewallMiddleware as g, type FirewallMode as h, type FirewallOptions as i,
|
|
238
|
+
export { ALL_HOOKS as A, type BlockResult as B, type ClassificationMetadata as C, DEFAULT_HOOKS as D, prependHook as E, FIREWALL_HOOK_TO_LABEL as F, type GovernanceAction as G, HookLabel as H, INPUT_HOOKS as I, prependToolName as J, type KnownHarmfulOutcome as K, type LangChainAdapterOptions as L, type MiddlewareOptions as M, resolveHooks as N, OUTCOME_DESCRIPTIONS as O, PRIMARY_OUTCOMES as P, createMiddleware as Q, type UnknownOutcome as U, type ClassifyBatchOptions as a, type ClassifyEvent as b, type ClassifyOptions as c, DEFAULT_TIMEOUT_MS as d, Firewall as e, FirewallHook as f, type FirewallMiddleware as g, type FirewallMode as h, type FirewallOptions as i, type GovernanceContext as j, type GovernanceDecision as k, type GovernanceResource as l, type GovernanceResourceKind as m, HARMFUL_OUTCOMES as n, type HarmfulOutcome as o, type KnownPrimaryOutcome as p, type LangChainFirewallHandler as q, OUTPUT_HOOKS as r, Outcome as s, type Prediction as t, type PrimaryOutcome as u, isHarmfulOutcome as v, isPrimaryOutcome as w, normalizeHarmfulOutcome as x, normalizeHarmfulOutcomeMap as y, normalizePrimaryOutcome as z };
|
package/dist/index.cjs
CHANGED
|
@@ -81,6 +81,9 @@ var init_exceptions = __esm({
|
|
|
81
81
|
}
|
|
82
82
|
static formatMessage(params) {
|
|
83
83
|
const truncated = params.promptText.length > MAX_PROMPT_DISPLAY_LEN ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...` : params.promptText;
|
|
84
|
+
if (params.result?.prediction !== "MALICIOUS" && params.result?.governance?.action === "block") {
|
|
85
|
+
return `Request blocked by Silmaril Firewall governance policy: '${truncated}'`;
|
|
86
|
+
}
|
|
84
87
|
return `Request blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
|
|
85
88
|
}
|
|
86
89
|
};
|
|
@@ -301,7 +304,7 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
301
304
|
return;
|
|
302
305
|
}
|
|
303
306
|
const threshold = result.threshold;
|
|
304
|
-
const blocked = result.prediction === "MALICIOUS";
|
|
307
|
+
const blocked = result.prediction === "MALICIOUS" || result.governance?.action === "block";
|
|
305
308
|
const effectiveMode = requestedMode ?? result.mode ?? "block";
|
|
306
309
|
const commonEventFields = {
|
|
307
310
|
hook: hookLabel,
|
|
@@ -556,7 +559,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
556
559
|
}
|
|
557
560
|
);
|
|
558
561
|
const threshold = result.threshold;
|
|
559
|
-
const blocked = result.prediction === "MALICIOUS";
|
|
562
|
+
const blocked = result.prediction === "MALICIOUS" || result.governance?.action === "block";
|
|
560
563
|
const effectiveMode = requestedMode ?? result.mode ?? "block";
|
|
561
564
|
const commonEventFields = {
|
|
562
565
|
hook,
|
|
@@ -808,7 +811,7 @@ function sanitizeText(text) {
|
|
|
808
811
|
}
|
|
809
812
|
|
|
810
813
|
// src/firewall.ts
|
|
811
|
-
var SDK_VERSION = "0.6.
|
|
814
|
+
var SDK_VERSION = "0.6.2";
|
|
812
815
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
813
816
|
var DEFAULT_MAX_RETRIES = 5;
|
|
814
817
|
var MAX_BACKOFF_SECONDS = 30;
|
|
@@ -859,6 +862,9 @@ function blockResultFromResponse(data, requestedMode) {
|
|
|
859
862
|
result.detectorCounts = detectorCounts;
|
|
860
863
|
}
|
|
861
864
|
}
|
|
865
|
+
if (data.governance !== void 0) {
|
|
866
|
+
result.governance = governanceDecisionFromResponse(data.governance);
|
|
867
|
+
}
|
|
862
868
|
return Object.freeze(result);
|
|
863
869
|
}
|
|
864
870
|
function isRecord2(value) {
|
|
@@ -875,10 +881,42 @@ function withSdkMetadata(metadata, info) {
|
|
|
875
881
|
sdk_language: "typescript",
|
|
876
882
|
sdk_version: SDK_VERSION,
|
|
877
883
|
request_id: info.requestId,
|
|
878
|
-
...info.inputIndex === void 0 ? {} : { input_index: info.inputIndex }
|
|
884
|
+
...info.inputIndex === void 0 ? {} : { input_index: info.inputIndex },
|
|
885
|
+
...info.governance === void 0 ? {} : { governance: governanceContextToWire(info.governance) }
|
|
879
886
|
};
|
|
880
887
|
return payload;
|
|
881
888
|
}
|
|
889
|
+
function governanceContextToWire(context) {
|
|
890
|
+
return {
|
|
891
|
+
...context.agent === void 0 ? {} : { agent: context.agent },
|
|
892
|
+
...context.resource === void 0 ? {} : {
|
|
893
|
+
resource: {
|
|
894
|
+
kind: context.resource.kind,
|
|
895
|
+
...context.resource.id === void 0 ? {} : { id: context.resource.id },
|
|
896
|
+
...context.resource.parentId === void 0 ? {} : { parent_id: context.resource.parentId }
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function governanceDecisionFromResponse(value) {
|
|
902
|
+
if (!isRecord2(value)) {
|
|
903
|
+
throw new Error("Firewall: response governance must be an object");
|
|
904
|
+
}
|
|
905
|
+
if (value.action !== "allow" && value.action !== "block") {
|
|
906
|
+
throw new Error("Firewall: response governance action must be allow or block");
|
|
907
|
+
}
|
|
908
|
+
if (typeof value.policy_version !== "string" || value.policy_version.length === 0) {
|
|
909
|
+
throw new Error("Firewall: response governance policy_version must be a non-empty string");
|
|
910
|
+
}
|
|
911
|
+
if (value.rule_id !== void 0 && typeof value.rule_id !== "string") {
|
|
912
|
+
throw new Error("Firewall: response governance rule_id must be a string when provided");
|
|
913
|
+
}
|
|
914
|
+
return Object.freeze({
|
|
915
|
+
action: value.action,
|
|
916
|
+
...value.rule_id === void 0 ? {} : { ruleId: value.rule_id },
|
|
917
|
+
policyVersion: value.policy_version
|
|
918
|
+
});
|
|
919
|
+
}
|
|
882
920
|
async function readCappedErrorBody(response) {
|
|
883
921
|
if (!response.body) {
|
|
884
922
|
return response.text().then((body2) => body2.slice(0, MAX_ERROR_BODY_BYTES)).catch(() => "");
|
|
@@ -966,6 +1004,11 @@ var Firewall = class {
|
|
|
966
1004
|
`Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
|
|
967
1005
|
);
|
|
968
1006
|
}
|
|
1007
|
+
if (options.governance !== void 0 && options.governance.length !== texts.length) {
|
|
1008
|
+
throw new Error(
|
|
1009
|
+
`Firewall: governance length ${options.governance.length} does not match texts length ${texts.length}`
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
969
1012
|
const requestId = options.requestId ?? (0, import_node_crypto.randomUUID)();
|
|
970
1013
|
const payload = {
|
|
971
1014
|
texts: texts.map((text) => sanitizeText(text))
|
|
@@ -983,7 +1026,8 @@ var Firewall = class {
|
|
|
983
1026
|
payload.metadata = texts.map(
|
|
984
1027
|
(_, index) => withSdkMetadata(options.metadata?.[index], {
|
|
985
1028
|
requestId,
|
|
986
|
-
inputIndex: index
|
|
1029
|
+
inputIndex: index,
|
|
1030
|
+
...options.governance?.[index] === void 0 ? {} : { governance: options.governance[index] }
|
|
987
1031
|
})
|
|
988
1032
|
);
|
|
989
1033
|
const data = await this.postWithRetry(payload);
|
|
@@ -1034,7 +1078,10 @@ var Firewall = class {
|
|
|
1034
1078
|
if (options.toolName !== void 0) {
|
|
1035
1079
|
payload.tool_name = options.toolName;
|
|
1036
1080
|
}
|
|
1037
|
-
payload.metadata = withSdkMetadata(options.metadata,
|
|
1081
|
+
payload.metadata = withSdkMetadata(options.metadata, {
|
|
1082
|
+
...metadataInfo,
|
|
1083
|
+
...options.governance === void 0 ? {} : { governance: options.governance }
|
|
1084
|
+
});
|
|
1038
1085
|
const data = await this.postWithRetry(payload);
|
|
1039
1086
|
return blockResultFromResponse(data, requestedMode);
|
|
1040
1087
|
}
|