@silmaril-security/sdk 0.4.2 → 0.5.1
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 +21 -0
- package/README.md +32 -27
- package/dist/adapters/langchain.cjs +1 -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 +1 -1
- package/dist/adapters/langchain.js.map +1 -1
- package/dist/adapters/vercel.cjs +1 -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 +1 -1
- package/dist/adapters/vercel.js.map +1 -1
- package/dist/{firewall-DfZCCDU6.d.cts → firewall-D2doJsD5.d.cts} +16 -7
- package/dist/{firewall-DfZCCDU6.d.ts → firewall-D2doJsD5.d.ts} +16 -7
- package/dist/index.cjs +73 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -12
- package/dist/index.d.ts +3 -12
- package/dist/index.js +72 -151
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
|
@@ -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 shadowMode = options.shadowMode ?? firewall.shadowMode;\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 toolName !== undefined ? { hook, toolName } : { hook },\n );\n const threshold = result.threshold;\n const blocked = result.score >= threshold;\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 shadowMode,\n });\n if (!blocked || shadowMode) {\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":";AAMA,IAAM,yBAAyB;AAWxB,IAAM,2BAAN,MAAM,kCAAiC,MAAM;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAST;AACD,UAAM,0BAAyB,cAAc,MAAM,CAAC;AACpD,SAAK,OAAO;AACZ,SAAK,QAAQ,OAAO;AACpB,SAAK,YAAY,OAAO;AACxB,SAAK,aAAa,OAAO;AACzB,SAAK,QAAQ,OAAO;AACpB,SAAK,OAAO,OAAO;AACnB,SAAK,WAAW,OAAO;AACvB,SAAK,aAAa,OAAO;AACzB,SAAK,SAAS,OAAO;AACrB,WAAO,eAAe,MAAM,0BAAyB,SAAS;AAAA,EAChE;AAAA,EAEA,OAAe,cAAc,QAIlB;AACT,UAAM,YACJ,OAAO,WAAW,SAAS,yBACvB,GAAG,OAAO,WAAW,MAAM,GAAG,sBAAsB,CAAC,QACrD,OAAO;AACb,WACE,+CACU,OAAO,MAAM,QAAQ,CAAC,CAAC,eAAe,OAAO,UAAU,QAAQ,CAAC,CAAC,OACvE,SAAS;AAAA,EAEjB;AACF;;;AC9DO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,SAAS;AACX;AAIO,IAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AAAA,EACV,eAAe;AACjB;AAIO,IAAM,gBAA2C,oBAAI,IAAkB;AAAA,EAC5E,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,cAAyC,oBAAI,IAAkB;AAAA,EAC1E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,eAA0C,oBAAI,IAAkB;AAAA,EAC3E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,YAAuC,oBAAI,IAAkB;AAAA,EACxE,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAEM,IAAM,yBAAoE;AAAA,EAC/E,CAAC,aAAa,gBAAgB,GAAG,UAAU;AAAA,EAC3C,CAAC,aAAa,SAAS,GAAG,UAAU;AAAA,EACpC,CAAC,aAAa,UAAU,GAAG,UAAU;AAAA,EACrC,CAAC,aAAa,QAAQ,GAAG,UAAU;AAAA,EACnC,CAAC,aAAa,eAAe,GAAG,UAAU;AAAA,EAC1C,CAAC,aAAa,aAAa,GAAG,UAAU;AAAA,EACxC,CAAC,aAAa,OAAO,GAAG,UAAU;AACpC;AAEA,IAAM,uBAA4C,IAAI,IAAI,OAAO,OAAO,YAAY,CAAC;;;ACArF,SAAS,mBAAmB,OAAwB;AAClD,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO,OAAO,KAAK;AAAA,EACrB;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,mBAAmB,SAAiD;AAC3E,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO;AAAA,EACT;AACA,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,SAAS;AAC3B,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,KAAK,KAAK;AAAA,IAClB,WAAW,SAAS,MAAM,SAAS,QAAQ;AACzC,YAAM,KAAK,MAAM,QAAQ,EAAE;AAAA,IAC7B;AAAA,EACF;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAEA,SAAS,uBACP,SACuF;AACvF,MAAI,OAAO,QAAQ,YAAY,UAAU;AACvC,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAA6F,CAAC;AACpG,aAAW,QAAQ,QAAQ,SAAS;AAClC,QAAI,OAAO,SAAS,UAAU;AAC5B;AAAA,IACF;AACA,QAAI,KAAK,SAAS,eAAe;AAC/B,YAAM,OAAO,oBAAoB,KAAK,WAAW,SAAY,KAAK,SAAS,KAAK,MAAM;AACtF,UAAI,KAAK,KAAK,GAAG;AACf,YAAI,KAAK,EAAE,MAAM,UAAU,KAAK,UAAU,YAAY,KAAK,WAAW,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAwB;AACnD,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO,mBAAmB,KAAK;AAAA,EACjC;AACA,OAAK,MAAM,SAAS,UAAU,MAAM,SAAS,iBAAiB,OAAO,MAAM,UAAU,UAAU;AAC7F,WAAO,MAAM;AAAA,EACf;AACA,OAAK,MAAM,SAAS,UAAU,MAAM,SAAS,iBAAiB,MAAM,UAAU,QAAW;AACvF,WAAO,mBAAmB,MAAM,KAAK;AAAA,EACvC;AACA,MAAI,MAAM,SAAS,aAAa,MAAM,QAAQ,MAAM,KAAK,GAAG;AAC1D,WAAO,MAAM,MACV,IAAI,CAAC,SAAU,SAAS,IAAI,KAAK,KAAK,SAAS,UAAU,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,EAAG,EACxG,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,KAAK,GAAG;AAAA,EACb;AACA,SAAO,mBAAmB,KAAK;AACjC;AAEA,SAAS,oBACP,UACiC;AACjC,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,IAAI,KAAK,YAAY,MAAM,QAAQ;AACrC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAaO,SAAS,iBACd,UACA,UAA6B,CAAC,GACV;AACpB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,aAAa,QAAQ,cAAc,SAAS;AAElD,QAAM,kBAAkB,OACtB,MACA,MACA,UAA6B,EAAE,UAAU,QAAW,YAAY,OAAU,MACxD;AAClB,QAAI,CAAC,KAAK,KAAK,GAAG;AAChB;AAAA,IACF;AACA,UAAM,EAAE,UAAU,WAAW,IAAI;AACjC,UAAM,SAAsB,MAAM,SAAS;AAAA,MACzC;AAAA,MACA,aAAa,SAAY,EAAE,MAAM,SAAS,IAAI,EAAE,KAAK;AAAA,IACvD;AACA,UAAM,YAAY,OAAO;AACzB,UAAM,UAAU,OAAO,SAAS;AAChC,UAAM,oBAAoB;AAAA,MACxB;AAAA,MACA,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD;AAAA,MACA;AAAA,IACF;AACA,YAAQ,aAAa;AAAA,MACnB,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,CAAC,WAAW,YAAY;AAC1B;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,yBAAyB;AAAA,MACvC,OAAO,OAAO;AAAA,MACd;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,MACA,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD;AAAA,IACF,CAAC;AACD,YAAQ,YAAY,GAAG;AACvB,UAAM;AAAA,EACR;AAEA,QAAM,aAAa,OAAO,WAA8D;AACtF,QAAI,OAAO,WAAW,GAAG;AACvB;AAAA,IACF;AACA,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,OAAO,KAAK,KAAK,YAAY;AAMnC,QAAI,SAAS,QAAQ;AACnB,iBAAW,EAAE,MAAAA,OAAM,UAAU,WAAW,KAAK,uBAAuB,IAAI,GAAG;AACzE,cAAM,gBAAgBA,OAAM,UAAU,eAAe,EAAE,UAAU,WAAW,CAAC;AAAA,MAC/E;AACA;AAAA,IACF;AAIA,UAAM,WAAW,oBAAoB,MAAM;AAC3C,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AACA,UAAM,OAAO,mBAAmB,SAAS,OAAO,EAAE,KAAK;AACvD,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AACA,UAAM,gBAAgB,MAAM,UAAU,UAAU;AAAA,EAClD;AAEA,QAAM,qBAAqB,OAAO,WAAgD;AAChF,QAAI,cAAc,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,GAAG;AAC3E,YAAM,gBAAgB,OAAO,MAAM,UAAU,UAAU;AAAA,IACzD;AACA,QAAI,QAAQ,iBAAiB,MAAM,QAAQ,OAAO,SAAS,GAAG;AAC5D,iBAAW,QAAQ,OAAO,WAAW;AACnC,YAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC;AAAA,QACF;AACA,cAAM,YAAY,KAAK,SAAS,SAAY,KAAK,OAAO,KAAK;AAC7D,cAAM,OACJ,OAAO,cAAc,WAAW,YAAY,mBAAmB,SAAS;AAC1E,cAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AACrE,cAAM,aAAa,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa;AAC3E,YAAI,KAAK,KAAK,GAAG;AACf,gBAAM,gBAAgB,MAAM,UAAU,WAAW,EAAE,UAAU,WAAW,CAAC;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,sBAAsB;AAAA,IACtB,mBAAmB;AAAA,IAEnB,MAAM,aAA6E;AAAA,MACjF;AAAA,MACA;AAAA,IACF,GAAgD;AAC9C,UAAI,WAAW;AACb,cAAM,WAAW,OAAO,UAAU,CAAC,CAAC;AAAA,MACtC;AACA,YAAM,SAAS,MAAM,WAAW;AAChC,YAAM,mBAAmB,MAAM;AAC/B,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,WAA0F;AAAA,MAC9F;AAAA,MACA;AAAA,IACF,GAA8C;AAC5C,UAAI,WAAW;AACb,cAAM,WAAW,OAAO,UAAU,CAAC,CAAC;AAAA,MACtC;AACA,YAAM,EAAE,QAAQ,GAAG,KAAK,IAAI,MAAM,SAAS;AAC3C,UAAI,CAAC,YAAY;AACf,eAAO,EAAE,QAAQ,GAAG,KAAK;AAAA,MAC3B;AAEA,UAAI,WAAW;AACf,YAAM,cAAc,OAAO;AAAA,QACzB,IAAI,gBAAkC;AAAA,UACpC,UAAU,OAAO,YAAkB;AACjC,kBAAM,OAAO;AACb,gBAAI,QAAQ,KAAK,SAAS,cAAc;AACtC,oBAAM,QAAQ,KAAK,aAAa,KAAK,SAAS;AAC9C,0BAAY;AAAA,YACd;AACA,uBAAW,QAAQ,KAAK;AAAA,UAC1B;AAAA,UACA,MAAM,MAAM,YAA2B;AACrC,gBAAI,CAAC,SAAS,KAAK,GAAG;AACpB;AAAA,YACF;AACA,gBAAI;AACF,oBAAM,gBAAgB,UAAU,UAAU,UAAU;AAAA,YACtD,SAAS,KAAK;AACZ,kBAAI,eAAe,0BAA0B;AAC3C,2BAAW,QAAQ,EAAE,MAAM,SAAS,OAAO,IAAI,CAAC;AAChD;AAAA,cACF;AACA,oBAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO,EAAE,QAAQ,aAAa,GAAG,KAAK;AAAA,IACxC;AAAA,EACF;AACF;","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 }): 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 shadowMode = options.shadowMode ?? firewall.shadowMode;\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 toolName !== undefined ? { hook, toolName } : { hook },\n );\n const threshold = result.threshold;\n const blocked = result.prediction === \"MALICIOUS\";\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 shadowMode,\n });\n if (!blocked || shadowMode) {\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"]}
|
|
@@ -33,6 +33,12 @@ declare const Outcome: {
|
|
|
33
33
|
readonly ControlAbuse: "control_abuse";
|
|
34
34
|
readonly SystemCompromise: "system_compromise";
|
|
35
35
|
readonly ServiceDisruption: "service_disruption";
|
|
36
|
+
readonly CodeGeneration: "code_generation";
|
|
37
|
+
readonly StoryScriptGeneration: "story_script_generation";
|
|
38
|
+
readonly GameGeneration: "game_generation";
|
|
39
|
+
readonly WebsiteGeneration: "website_generation";
|
|
40
|
+
readonly ClickUpTermsViolation: "clickup_terms_violation";
|
|
41
|
+
readonly TraditionalAiAbuse: "traditional_ai_abuse";
|
|
36
42
|
};
|
|
37
43
|
declare const unknownOutcomeBrand: unique symbol;
|
|
38
44
|
type KnownPrimaryOutcome = (typeof Outcome)[keyof typeof Outcome];
|
|
@@ -42,8 +48,8 @@ type UnknownOutcome = string & {
|
|
|
42
48
|
};
|
|
43
49
|
type PrimaryOutcome = KnownPrimaryOutcome | UnknownOutcome;
|
|
44
50
|
type HarmfulOutcome = KnownHarmfulOutcome | UnknownOutcome;
|
|
45
|
-
declare const PRIMARY_OUTCOMES: readonly ["benign", "information_disclosure", "secret_exposure", "control_abuse", "system_compromise", "service_disruption"];
|
|
46
|
-
declare const HARMFUL_OUTCOMES: readonly ["information_disclosure", "secret_exposure", "control_abuse", "system_compromise", "service_disruption"];
|
|
51
|
+
declare const PRIMARY_OUTCOMES: readonly ["benign", "information_disclosure", "secret_exposure", "control_abuse", "system_compromise", "service_disruption", "code_generation", "story_script_generation", "game_generation", "website_generation", "clickup_terms_violation", "traditional_ai_abuse"];
|
|
52
|
+
declare const HARMFUL_OUTCOMES: readonly ["information_disclosure", "secret_exposure", "control_abuse", "system_compromise", "service_disruption", "code_generation", "story_script_generation", "game_generation", "website_generation", "clickup_terms_violation", "traditional_ai_abuse"];
|
|
47
53
|
declare const OUTCOME_DESCRIPTIONS: {
|
|
48
54
|
readonly benign: "No harmful firewall outcome detected.";
|
|
49
55
|
readonly information_disclosure: "Exposes private data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.";
|
|
@@ -51,6 +57,12 @@ declare const OUTCOME_DESCRIPTIONS: {
|
|
|
51
57
|
readonly control_abuse: "Misuses authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.";
|
|
52
58
|
readonly system_compromise: "Enables privilege escalation, account takeover, hostile integration or plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.";
|
|
53
59
|
readonly service_disruption: "Causes downtime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence.";
|
|
60
|
+
readonly code_generation: "Requests generation or material modification of executable code, scripts, workflows, or configuration.";
|
|
61
|
+
readonly story_script_generation: "Requests generation of narrative prose, dialogue, scripts, or story artifacts.";
|
|
62
|
+
readonly game_generation: "Requests generation of a game, quest, level, mechanic, or playable experience.";
|
|
63
|
+
readonly website_generation: "Requests generation of a website, landing page, storefront, or web experience.";
|
|
64
|
+
readonly clickup_terms_violation: "Requests content or actions that violate the configured ClickUp tenant policy.";
|
|
65
|
+
readonly traditional_ai_abuse: "Requests unsafe AI assistance outside the concrete security outcome classes.";
|
|
54
66
|
};
|
|
55
67
|
declare function isPrimaryOutcome(value: unknown): value is KnownPrimaryOutcome;
|
|
56
68
|
declare function isHarmfulOutcome(value: unknown): value is KnownHarmfulOutcome;
|
|
@@ -72,7 +84,6 @@ interface FirewallOptions {
|
|
|
72
84
|
apiKey: string;
|
|
73
85
|
apiUrl: string;
|
|
74
86
|
timeoutMs?: number;
|
|
75
|
-
chunkConcurrency?: number;
|
|
76
87
|
shadowMode?: boolean;
|
|
77
88
|
}
|
|
78
89
|
type ClassificationMetadata = Readonly<Record<string, unknown>>;
|
|
@@ -177,12 +188,10 @@ interface FirewallMiddleware {
|
|
|
177
188
|
declare function createMiddleware(firewall: Firewall, options?: MiddlewareOptions): FirewallMiddleware;
|
|
178
189
|
|
|
179
190
|
declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
180
|
-
declare const DEFAULT_CHUNK_CONCURRENCY = 8;
|
|
181
191
|
declare class Firewall {
|
|
182
192
|
readonly apiKey: string;
|
|
183
193
|
readonly apiUrl: string;
|
|
184
194
|
readonly timeoutMs: number;
|
|
185
|
-
readonly chunkConcurrency: number;
|
|
186
195
|
readonly shadowMode: boolean;
|
|
187
196
|
private readonly headers;
|
|
188
197
|
constructor(options: FirewallOptions);
|
|
@@ -191,7 +200,7 @@ declare class Firewall {
|
|
|
191
200
|
asLangChainHandler<THandler = LangChainFirewallHandler>(options?: LangChainAdapterOptions): Promise<THandler>;
|
|
192
201
|
asMiddleware(options?: MiddlewareOptions): FirewallMiddleware;
|
|
193
202
|
private postWithRetry;
|
|
194
|
-
private
|
|
203
|
+
private classifySingle;
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
export { ALL_HOOKS as A, type BlockResult as B, type ClassificationMetadata as C,
|
|
206
|
+
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 FirewallOptions as h, HARMFUL_OUTCOMES as i, type HarmfulOutcome as j, type KnownPrimaryOutcome as k, type LangChainFirewallHandler as l, OUTPUT_HOOKS as m, Outcome as n, type Prediction as o, type PrimaryOutcome as p, isHarmfulOutcome as q, isPrimaryOutcome as r, normalizeHarmfulOutcome as s, normalizeHarmfulOutcomeMap as t, normalizePrimaryOutcome as u, prependHook as v, prependToolName as w, resolveHooks as x, createMiddleware as y };
|
|
@@ -33,6 +33,12 @@ declare const Outcome: {
|
|
|
33
33
|
readonly ControlAbuse: "control_abuse";
|
|
34
34
|
readonly SystemCompromise: "system_compromise";
|
|
35
35
|
readonly ServiceDisruption: "service_disruption";
|
|
36
|
+
readonly CodeGeneration: "code_generation";
|
|
37
|
+
readonly StoryScriptGeneration: "story_script_generation";
|
|
38
|
+
readonly GameGeneration: "game_generation";
|
|
39
|
+
readonly WebsiteGeneration: "website_generation";
|
|
40
|
+
readonly ClickUpTermsViolation: "clickup_terms_violation";
|
|
41
|
+
readonly TraditionalAiAbuse: "traditional_ai_abuse";
|
|
36
42
|
};
|
|
37
43
|
declare const unknownOutcomeBrand: unique symbol;
|
|
38
44
|
type KnownPrimaryOutcome = (typeof Outcome)[keyof typeof Outcome];
|
|
@@ -42,8 +48,8 @@ type UnknownOutcome = string & {
|
|
|
42
48
|
};
|
|
43
49
|
type PrimaryOutcome = KnownPrimaryOutcome | UnknownOutcome;
|
|
44
50
|
type HarmfulOutcome = KnownHarmfulOutcome | UnknownOutcome;
|
|
45
|
-
declare const PRIMARY_OUTCOMES: readonly ["benign", "information_disclosure", "secret_exposure", "control_abuse", "system_compromise", "service_disruption"];
|
|
46
|
-
declare const HARMFUL_OUTCOMES: readonly ["information_disclosure", "secret_exposure", "control_abuse", "system_compromise", "service_disruption"];
|
|
51
|
+
declare const PRIMARY_OUTCOMES: readonly ["benign", "information_disclosure", "secret_exposure", "control_abuse", "system_compromise", "service_disruption", "code_generation", "story_script_generation", "game_generation", "website_generation", "clickup_terms_violation", "traditional_ai_abuse"];
|
|
52
|
+
declare const HARMFUL_OUTCOMES: readonly ["information_disclosure", "secret_exposure", "control_abuse", "system_compromise", "service_disruption", "code_generation", "story_script_generation", "game_generation", "website_generation", "clickup_terms_violation", "traditional_ai_abuse"];
|
|
47
53
|
declare const OUTCOME_DESCRIPTIONS: {
|
|
48
54
|
readonly benign: "No harmful firewall outcome detected.";
|
|
49
55
|
readonly information_disclosure: "Exposes private data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.";
|
|
@@ -51,6 +57,12 @@ declare const OUTCOME_DESCRIPTIONS: {
|
|
|
51
57
|
readonly control_abuse: "Misuses authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.";
|
|
52
58
|
readonly system_compromise: "Enables privilege escalation, account takeover, hostile integration or plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.";
|
|
53
59
|
readonly service_disruption: "Causes downtime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence.";
|
|
60
|
+
readonly code_generation: "Requests generation or material modification of executable code, scripts, workflows, or configuration.";
|
|
61
|
+
readonly story_script_generation: "Requests generation of narrative prose, dialogue, scripts, or story artifacts.";
|
|
62
|
+
readonly game_generation: "Requests generation of a game, quest, level, mechanic, or playable experience.";
|
|
63
|
+
readonly website_generation: "Requests generation of a website, landing page, storefront, or web experience.";
|
|
64
|
+
readonly clickup_terms_violation: "Requests content or actions that violate the configured ClickUp tenant policy.";
|
|
65
|
+
readonly traditional_ai_abuse: "Requests unsafe AI assistance outside the concrete security outcome classes.";
|
|
54
66
|
};
|
|
55
67
|
declare function isPrimaryOutcome(value: unknown): value is KnownPrimaryOutcome;
|
|
56
68
|
declare function isHarmfulOutcome(value: unknown): value is KnownHarmfulOutcome;
|
|
@@ -72,7 +84,6 @@ interface FirewallOptions {
|
|
|
72
84
|
apiKey: string;
|
|
73
85
|
apiUrl: string;
|
|
74
86
|
timeoutMs?: number;
|
|
75
|
-
chunkConcurrency?: number;
|
|
76
87
|
shadowMode?: boolean;
|
|
77
88
|
}
|
|
78
89
|
type ClassificationMetadata = Readonly<Record<string, unknown>>;
|
|
@@ -177,12 +188,10 @@ interface FirewallMiddleware {
|
|
|
177
188
|
declare function createMiddleware(firewall: Firewall, options?: MiddlewareOptions): FirewallMiddleware;
|
|
178
189
|
|
|
179
190
|
declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
180
|
-
declare const DEFAULT_CHUNK_CONCURRENCY = 8;
|
|
181
191
|
declare class Firewall {
|
|
182
192
|
readonly apiKey: string;
|
|
183
193
|
readonly apiUrl: string;
|
|
184
194
|
readonly timeoutMs: number;
|
|
185
|
-
readonly chunkConcurrency: number;
|
|
186
195
|
readonly shadowMode: boolean;
|
|
187
196
|
private readonly headers;
|
|
188
197
|
constructor(options: FirewallOptions);
|
|
@@ -191,7 +200,7 @@ declare class Firewall {
|
|
|
191
200
|
asLangChainHandler<THandler = LangChainFirewallHandler>(options?: LangChainAdapterOptions): Promise<THandler>;
|
|
192
201
|
asMiddleware(options?: MiddlewareOptions): FirewallMiddleware;
|
|
193
202
|
private postWithRetry;
|
|
194
|
-
private
|
|
203
|
+
private classifySingle;
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
export { ALL_HOOKS as A, type BlockResult as B, type ClassificationMetadata as C,
|
|
206
|
+
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 FirewallOptions as h, HARMFUL_OUTCOMES as i, type HarmfulOutcome as j, type KnownPrimaryOutcome as k, type LangChainFirewallHandler as l, OUTPUT_HOOKS as m, Outcome as n, type Prediction as o, type PrimaryOutcome as p, isHarmfulOutcome as q, isPrimaryOutcome as r, normalizeHarmfulOutcome as s, normalizeHarmfulOutcomeMap as t, normalizePrimaryOutcome as u, prependHook as v, prependToolName as w, resolveHooks as x, createMiddleware as y };
|
package/dist/index.cjs
CHANGED
|
@@ -5,8 +5,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
|
|
8
|
+
var __esm = (fn, res, err) => function __init() {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
10
15
|
};
|
|
11
16
|
var __export = (target, all) => {
|
|
12
17
|
for (var name in all)
|
|
@@ -295,7 +300,7 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
295
300
|
return;
|
|
296
301
|
}
|
|
297
302
|
const threshold = result.threshold;
|
|
298
|
-
const blocked = result.
|
|
303
|
+
const blocked = result.prediction === "MALICIOUS";
|
|
299
304
|
const commonEventFields = {
|
|
300
305
|
hook: hookLabel,
|
|
301
306
|
...toolName !== void 0 ? { toolName } : {},
|
|
@@ -426,11 +431,6 @@ var init_langchain = __esm({
|
|
|
426
431
|
var src_exports = {};
|
|
427
432
|
__export(src_exports, {
|
|
428
433
|
ALL_HOOKS: () => ALL_HOOKS,
|
|
429
|
-
CHUNK_OVERLAP: () => CHUNK_OVERLAP,
|
|
430
|
-
CHUNK_OVERLAP_CHARS: () => CHUNK_OVERLAP_CHARS,
|
|
431
|
-
CHUNK_WINDOW: () => CHUNK_WINDOW,
|
|
432
|
-
CHUNK_WINDOW_CHARS: () => CHUNK_WINDOW_CHARS,
|
|
433
|
-
DEFAULT_CHUNK_CONCURRENCY: () => DEFAULT_CHUNK_CONCURRENCY,
|
|
434
434
|
DEFAULT_HOOKS: () => DEFAULT_HOOKS,
|
|
435
435
|
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
436
436
|
FIREWALL_HOOK_TO_LABEL: () => FIREWALL_HOOK_TO_LABEL,
|
|
@@ -440,15 +440,12 @@ __export(src_exports, {
|
|
|
440
440
|
HARMFUL_OUTCOMES: () => HARMFUL_OUTCOMES,
|
|
441
441
|
HookLabel: () => HookLabel,
|
|
442
442
|
INPUT_HOOKS: () => INPUT_HOOKS,
|
|
443
|
-
MAX_INPUT_CHARS: () => MAX_INPUT_CHARS,
|
|
444
|
-
MAX_INPUT_TOKENS: () => MAX_INPUT_TOKENS,
|
|
445
443
|
OUTCOME_DESCRIPTIONS: () => OUTCOME_DESCRIPTIONS,
|
|
446
444
|
OUTPUT_HOOKS: () => OUTPUT_HOOKS,
|
|
447
445
|
Outcome: () => Outcome,
|
|
448
446
|
PRIMARY_OUTCOMES: () => PRIMARY_OUTCOMES,
|
|
449
447
|
PromptBlockedException: () => PromptBlockedException,
|
|
450
448
|
SilmarilApiError: () => SilmarilApiError,
|
|
451
|
-
chunkText: () => chunkText,
|
|
452
449
|
isHarmfulOutcome: () => isHarmfulOutcome,
|
|
453
450
|
isPrimaryOutcome: () => isPrimaryOutcome,
|
|
454
451
|
normalizeHarmfulOutcome: () => normalizeHarmfulOutcome,
|
|
@@ -456,8 +453,7 @@ __export(src_exports, {
|
|
|
456
453
|
normalizePrimaryOutcome: () => normalizePrimaryOutcome,
|
|
457
454
|
prependHook: () => prependHook,
|
|
458
455
|
prependToolName: () => prependToolName,
|
|
459
|
-
resolveHooks: () => resolveHooks
|
|
460
|
-
sanitizeText: () => sanitizeText
|
|
456
|
+
resolveHooks: () => resolveHooks
|
|
461
457
|
});
|
|
462
458
|
module.exports = __toCommonJS(src_exports);
|
|
463
459
|
|
|
@@ -553,7 +549,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
553
549
|
toolName !== void 0 ? { hook, toolName } : { hook }
|
|
554
550
|
);
|
|
555
551
|
const threshold = result.threshold;
|
|
556
|
-
const blocked = result.
|
|
552
|
+
const blocked = result.prediction === "MALICIOUS";
|
|
557
553
|
const commonEventFields = {
|
|
558
554
|
hook,
|
|
559
555
|
...toolName !== void 0 ? { toolName } : {},
|
|
@@ -679,78 +675,6 @@ function createMiddleware(firewall, options = {}) {
|
|
|
679
675
|
};
|
|
680
676
|
}
|
|
681
677
|
|
|
682
|
-
// src/chunking.ts
|
|
683
|
-
var CHARS_PER_TOKEN = 4;
|
|
684
|
-
var MAX_INPUT_TOKENS = 81920;
|
|
685
|
-
var CHUNK_WINDOW = 400;
|
|
686
|
-
var CHUNK_OVERLAP = 64;
|
|
687
|
-
var MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;
|
|
688
|
-
var CHUNK_WINDOW_CHARS = CHUNK_WINDOW * CHARS_PER_TOKEN;
|
|
689
|
-
var CHUNK_OVERLAP_CHARS = CHUNK_OVERLAP * CHARS_PER_TOKEN;
|
|
690
|
-
var CHUNK_STRIDE_CHARS = CHUNK_WINDOW_CHARS - CHUNK_OVERLAP_CHARS;
|
|
691
|
-
function isHighSurrogate(code) {
|
|
692
|
-
return code >= 55296 && code <= 56319;
|
|
693
|
-
}
|
|
694
|
-
function isLowSurrogate(code) {
|
|
695
|
-
return code >= 56320 && code <= 57343;
|
|
696
|
-
}
|
|
697
|
-
function sanitizeText(text) {
|
|
698
|
-
let out = "";
|
|
699
|
-
for (let i = 0; i < text.length; i++) {
|
|
700
|
-
const code = text.charCodeAt(i);
|
|
701
|
-
if (isHighSurrogate(code)) {
|
|
702
|
-
if (i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {
|
|
703
|
-
out += text[i];
|
|
704
|
-
out += text[i + 1];
|
|
705
|
-
i++;
|
|
706
|
-
}
|
|
707
|
-
continue;
|
|
708
|
-
}
|
|
709
|
-
if (isLowSurrogate(code)) {
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
out += text[i];
|
|
713
|
-
}
|
|
714
|
-
return out;
|
|
715
|
-
}
|
|
716
|
-
function safeChunkStart(text, start) {
|
|
717
|
-
if (start > 0 && start < text.length && isLowSurrogate(text.charCodeAt(start)) && isHighSurrogate(text.charCodeAt(start - 1))) {
|
|
718
|
-
return start + 1;
|
|
719
|
-
}
|
|
720
|
-
return start;
|
|
721
|
-
}
|
|
722
|
-
function safeChunkEnd(text, end) {
|
|
723
|
-
if (end > 0 && end < text.length && isHighSurrogate(text.charCodeAt(end - 1)) && isLowSurrogate(text.charCodeAt(end))) {
|
|
724
|
-
return end - 1;
|
|
725
|
-
}
|
|
726
|
-
return end;
|
|
727
|
-
}
|
|
728
|
-
function chunkText(text) {
|
|
729
|
-
text = sanitizeText(text);
|
|
730
|
-
const n = text.length;
|
|
731
|
-
if (n > MAX_INPUT_CHARS) {
|
|
732
|
-
throw new Error(
|
|
733
|
-
`Input has ~${Math.floor(n / CHARS_PER_TOKEN)} tokens (${n} chars); max is ${MAX_INPUT_TOKENS} tokens (${MAX_INPUT_CHARS} chars)`
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
if (n <= CHUNK_WINDOW_CHARS) {
|
|
737
|
-
return [text];
|
|
738
|
-
}
|
|
739
|
-
const chunks = [];
|
|
740
|
-
for (let start = 0; start < n; start += CHUNK_STRIDE_CHARS) {
|
|
741
|
-
const safeStart = safeChunkStart(text, start);
|
|
742
|
-
if (safeStart >= n) {
|
|
743
|
-
break;
|
|
744
|
-
}
|
|
745
|
-
const end = safeChunkEnd(text, safeStart + CHUNK_WINDOW_CHARS);
|
|
746
|
-
chunks.push(text.slice(safeStart, end));
|
|
747
|
-
if (end >= n) {
|
|
748
|
-
break;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
return chunks;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
678
|
// src/firewall.ts
|
|
755
679
|
init_exceptions();
|
|
756
680
|
|
|
@@ -761,7 +685,13 @@ var Outcome = {
|
|
|
761
685
|
SecretExposure: "secret_exposure",
|
|
762
686
|
ControlAbuse: "control_abuse",
|
|
763
687
|
SystemCompromise: "system_compromise",
|
|
764
|
-
ServiceDisruption: "service_disruption"
|
|
688
|
+
ServiceDisruption: "service_disruption",
|
|
689
|
+
CodeGeneration: "code_generation",
|
|
690
|
+
StoryScriptGeneration: "story_script_generation",
|
|
691
|
+
GameGeneration: "game_generation",
|
|
692
|
+
WebsiteGeneration: "website_generation",
|
|
693
|
+
ClickUpTermsViolation: "clickup_terms_violation",
|
|
694
|
+
TraditionalAiAbuse: "traditional_ai_abuse"
|
|
765
695
|
};
|
|
766
696
|
var PRIMARY_OUTCOMES = [
|
|
767
697
|
Outcome.Benign,
|
|
@@ -769,14 +699,26 @@ var PRIMARY_OUTCOMES = [
|
|
|
769
699
|
Outcome.SecretExposure,
|
|
770
700
|
Outcome.ControlAbuse,
|
|
771
701
|
Outcome.SystemCompromise,
|
|
772
|
-
Outcome.ServiceDisruption
|
|
702
|
+
Outcome.ServiceDisruption,
|
|
703
|
+
Outcome.CodeGeneration,
|
|
704
|
+
Outcome.StoryScriptGeneration,
|
|
705
|
+
Outcome.GameGeneration,
|
|
706
|
+
Outcome.WebsiteGeneration,
|
|
707
|
+
Outcome.ClickUpTermsViolation,
|
|
708
|
+
Outcome.TraditionalAiAbuse
|
|
773
709
|
];
|
|
774
710
|
var HARMFUL_OUTCOMES = [
|
|
775
711
|
Outcome.InformationDisclosure,
|
|
776
712
|
Outcome.SecretExposure,
|
|
777
713
|
Outcome.ControlAbuse,
|
|
778
714
|
Outcome.SystemCompromise,
|
|
779
|
-
Outcome.ServiceDisruption
|
|
715
|
+
Outcome.ServiceDisruption,
|
|
716
|
+
Outcome.CodeGeneration,
|
|
717
|
+
Outcome.StoryScriptGeneration,
|
|
718
|
+
Outcome.GameGeneration,
|
|
719
|
+
Outcome.WebsiteGeneration,
|
|
720
|
+
Outcome.ClickUpTermsViolation,
|
|
721
|
+
Outcome.TraditionalAiAbuse
|
|
780
722
|
];
|
|
781
723
|
var OUTCOME_DESCRIPTIONS = {
|
|
782
724
|
[Outcome.Benign]: "No harmful firewall outcome detected.",
|
|
@@ -784,7 +726,13 @@ var OUTCOME_DESCRIPTIONS = {
|
|
|
784
726
|
[Outcome.SecretExposure]: "Exposes credentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.",
|
|
785
727
|
[Outcome.ControlAbuse]: "Misuses authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.",
|
|
786
728
|
[Outcome.SystemCompromise]: "Enables privilege escalation, account takeover, hostile integration or plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.",
|
|
787
|
-
[Outcome.ServiceDisruption]: "Causes downtime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence."
|
|
729
|
+
[Outcome.ServiceDisruption]: "Causes downtime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence.",
|
|
730
|
+
[Outcome.CodeGeneration]: "Requests generation or material modification of executable code, scripts, workflows, or configuration.",
|
|
731
|
+
[Outcome.StoryScriptGeneration]: "Requests generation of narrative prose, dialogue, scripts, or story artifacts.",
|
|
732
|
+
[Outcome.GameGeneration]: "Requests generation of a game, quest, level, mechanic, or playable experience.",
|
|
733
|
+
[Outcome.WebsiteGeneration]: "Requests generation of a website, landing page, storefront, or web experience.",
|
|
734
|
+
[Outcome.ClickUpTermsViolation]: "Requests content or actions that violate the configured ClickUp tenant policy.",
|
|
735
|
+
[Outcome.TraditionalAiAbuse]: "Requests unsafe AI assistance outside the concrete security outcome classes."
|
|
788
736
|
};
|
|
789
737
|
var PRIMARY_OUTCOME_SET = new Set(PRIMARY_OUTCOMES);
|
|
790
738
|
var HARMFUL_OUTCOME_SET = new Set(HARMFUL_OUTCOMES);
|
|
@@ -824,14 +772,42 @@ function normalizeHarmfulOutcomeMap(values, fieldName) {
|
|
|
824
772
|
return Object.freeze(out);
|
|
825
773
|
}
|
|
826
774
|
|
|
775
|
+
// src/sanitization.ts
|
|
776
|
+
function isHighSurrogate(code) {
|
|
777
|
+
return code >= 55296 && code <= 56319;
|
|
778
|
+
}
|
|
779
|
+
function isLowSurrogate(code) {
|
|
780
|
+
return code >= 56320 && code <= 57343;
|
|
781
|
+
}
|
|
782
|
+
function sanitizeText(text) {
|
|
783
|
+
let out = "";
|
|
784
|
+
for (let i = 0; i < text.length; i++) {
|
|
785
|
+
const code = text.charCodeAt(i);
|
|
786
|
+
if (isHighSurrogate(code)) {
|
|
787
|
+
if (i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {
|
|
788
|
+
out += text[i];
|
|
789
|
+
out += text[i + 1];
|
|
790
|
+
i++;
|
|
791
|
+
}
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (!isLowSurrogate(code)) {
|
|
795
|
+
out += text[i];
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return out;
|
|
799
|
+
}
|
|
800
|
+
|
|
827
801
|
// src/firewall.ts
|
|
828
|
-
var SDK_VERSION = "0.
|
|
802
|
+
var SDK_VERSION = "0.5.1";
|
|
829
803
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
830
|
-
var DEFAULT_CHUNK_CONCURRENCY = 8;
|
|
831
804
|
var DEFAULT_MAX_RETRIES = 5;
|
|
832
805
|
var MAX_BACKOFF_SECONDS = 30;
|
|
833
806
|
var MAX_ERROR_BODY_BYTES = 1 << 16;
|
|
834
807
|
function blockResultFromResponse(data) {
|
|
808
|
+
if (data.prediction !== "BENIGN" && data.prediction !== "MALICIOUS") {
|
|
809
|
+
throw new Error("Firewall: response prediction must be BENIGN or MALICIOUS");
|
|
810
|
+
}
|
|
835
811
|
const result = {
|
|
836
812
|
prediction: data.prediction,
|
|
837
813
|
score: Number(data.score),
|
|
@@ -874,9 +850,7 @@ function withSdkMetadata(metadata, info) {
|
|
|
874
850
|
sdk_language: "typescript",
|
|
875
851
|
sdk_version: SDK_VERSION,
|
|
876
852
|
request_id: info.requestId,
|
|
877
|
-
input_index: info.inputIndex
|
|
878
|
-
chunk_index: info.chunkIndex,
|
|
879
|
-
chunk_count: info.chunkCount
|
|
853
|
+
...info.inputIndex === void 0 ? {} : { input_index: info.inputIndex }
|
|
880
854
|
};
|
|
881
855
|
return payload;
|
|
882
856
|
}
|
|
@@ -917,36 +891,10 @@ async function readCappedErrorBody(response) {
|
|
|
917
891
|
}
|
|
918
892
|
return new TextDecoder().decode(body);
|
|
919
893
|
}
|
|
920
|
-
async function mapWithConcurrency(items, concurrency, task) {
|
|
921
|
-
const results = new Array(items.length);
|
|
922
|
-
let nextIndex = 0;
|
|
923
|
-
let firstError;
|
|
924
|
-
const workerCount = Math.min(concurrency, items.length);
|
|
925
|
-
async function worker() {
|
|
926
|
-
for (; ; ) {
|
|
927
|
-
const index = nextIndex;
|
|
928
|
-
nextIndex += 1;
|
|
929
|
-
if (index >= items.length) {
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
try {
|
|
933
|
-
results[index] = await task(items[index], index);
|
|
934
|
-
} catch (err) {
|
|
935
|
-
firstError ??= err;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
940
|
-
if (firstError !== void 0) {
|
|
941
|
-
throw firstError;
|
|
942
|
-
}
|
|
943
|
-
return results;
|
|
944
|
-
}
|
|
945
894
|
var Firewall = class {
|
|
946
895
|
apiKey;
|
|
947
896
|
apiUrl;
|
|
948
897
|
timeoutMs;
|
|
949
|
-
chunkConcurrency;
|
|
950
898
|
shadowMode;
|
|
951
899
|
headers;
|
|
952
900
|
constructor(options) {
|
|
@@ -962,12 +910,6 @@ var Firewall = class {
|
|
|
962
910
|
if (typeof this.timeoutMs !== "number" || !Number.isFinite(this.timeoutMs) || this.timeoutMs < 0) {
|
|
963
911
|
throw new Error(`Firewall: timeoutMs must be a finite non-negative number, got ${this.timeoutMs}`);
|
|
964
912
|
}
|
|
965
|
-
this.chunkConcurrency = options.chunkConcurrency ?? DEFAULT_CHUNK_CONCURRENCY;
|
|
966
|
-
if (!Number.isInteger(this.chunkConcurrency) || this.chunkConcurrency < 1) {
|
|
967
|
-
throw new Error(
|
|
968
|
-
`Firewall: chunkConcurrency must be an integer >= 1, got ${this.chunkConcurrency}`
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
913
|
this.shadowMode = options.shadowMode ?? false;
|
|
972
914
|
this.headers = Object.freeze({
|
|
973
915
|
"x-api-key": this.apiKey,
|
|
@@ -975,27 +917,8 @@ var Firewall = class {
|
|
|
975
917
|
});
|
|
976
918
|
}
|
|
977
919
|
async classify(text, options = {}) {
|
|
978
|
-
const chunks = chunkText(text);
|
|
979
920
|
const requestId = options.requestId ?? (0, import_node_crypto.randomUUID)();
|
|
980
|
-
|
|
981
|
-
return this.classifySingleChunk(chunks[0], options, {
|
|
982
|
-
requestId,
|
|
983
|
-
inputIndex: 0,
|
|
984
|
-
chunkIndex: 0,
|
|
985
|
-
chunkCount: 1
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
const results = await mapWithConcurrency(
|
|
989
|
-
chunks,
|
|
990
|
-
this.chunkConcurrency,
|
|
991
|
-
(chunk, index) => this.classifySingleChunk(chunk, options, {
|
|
992
|
-
requestId,
|
|
993
|
-
inputIndex: 0,
|
|
994
|
-
chunkIndex: index,
|
|
995
|
-
chunkCount: chunks.length
|
|
996
|
-
})
|
|
997
|
-
);
|
|
998
|
-
return results.reduce((best, r) => r.score > best.score ? r : best);
|
|
921
|
+
return this.classifySingle(sanitizeText(text), options, { requestId });
|
|
999
922
|
}
|
|
1000
923
|
async classifyBatch(texts, options = {}) {
|
|
1001
924
|
if (texts.length === 0) {
|
|
@@ -1029,9 +952,7 @@ var Firewall = class {
|
|
|
1029
952
|
payload.metadata = texts.map(
|
|
1030
953
|
(_, index) => withSdkMetadata(options.metadata?.[index], {
|
|
1031
954
|
requestId,
|
|
1032
|
-
inputIndex: index
|
|
1033
|
-
chunkIndex: 0,
|
|
1034
|
-
chunkCount: 1
|
|
955
|
+
inputIndex: index
|
|
1035
956
|
})
|
|
1036
957
|
);
|
|
1037
958
|
const data = await this.postWithRetry(payload);
|
|
@@ -1070,7 +991,7 @@ var Firewall = class {
|
|
|
1070
991
|
}
|
|
1071
992
|
throw new Error("Firewall: exhausted retries (unreachable)");
|
|
1072
993
|
}
|
|
1073
|
-
async
|
|
994
|
+
async classifySingle(text, options, metadataInfo) {
|
|
1074
995
|
const payload = { text };
|
|
1075
996
|
if (options.hook !== void 0) {
|
|
1076
997
|
payload.hook = options.hook;
|
|
@@ -1090,11 +1011,6 @@ init_hooks();
|
|
|
1090
1011
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1091
1012
|
0 && (module.exports = {
|
|
1092
1013
|
ALL_HOOKS,
|
|
1093
|
-
CHUNK_OVERLAP,
|
|
1094
|
-
CHUNK_OVERLAP_CHARS,
|
|
1095
|
-
CHUNK_WINDOW,
|
|
1096
|
-
CHUNK_WINDOW_CHARS,
|
|
1097
|
-
DEFAULT_CHUNK_CONCURRENCY,
|
|
1098
1014
|
DEFAULT_HOOKS,
|
|
1099
1015
|
DEFAULT_TIMEOUT_MS,
|
|
1100
1016
|
FIREWALL_HOOK_TO_LABEL,
|
|
@@ -1104,15 +1020,12 @@ init_hooks();
|
|
|
1104
1020
|
HARMFUL_OUTCOMES,
|
|
1105
1021
|
HookLabel,
|
|
1106
1022
|
INPUT_HOOKS,
|
|
1107
|
-
MAX_INPUT_CHARS,
|
|
1108
|
-
MAX_INPUT_TOKENS,
|
|
1109
1023
|
OUTCOME_DESCRIPTIONS,
|
|
1110
1024
|
OUTPUT_HOOKS,
|
|
1111
1025
|
Outcome,
|
|
1112
1026
|
PRIMARY_OUTCOMES,
|
|
1113
1027
|
PromptBlockedException,
|
|
1114
1028
|
SilmarilApiError,
|
|
1115
|
-
chunkText,
|
|
1116
1029
|
isHarmfulOutcome,
|
|
1117
1030
|
isPrimaryOutcome,
|
|
1118
1031
|
normalizeHarmfulOutcome,
|
|
@@ -1120,7 +1033,6 @@ init_hooks();
|
|
|
1120
1033
|
normalizePrimaryOutcome,
|
|
1121
1034
|
prependHook,
|
|
1122
1035
|
prependToolName,
|
|
1123
|
-
resolveHooks
|
|
1124
|
-
sanitizeText
|
|
1036
|
+
resolveHooks
|
|
1125
1037
|
});
|
|
1126
1038
|
//# sourceMappingURL=index.cjs.map
|