@silmaril-security/sdk 0.3.1 → 0.4.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 +30 -0
- package/README.md +131 -32
- package/dist/adapters/langchain.cjs +349 -0
- package/dist/adapters/langchain.cjs.map +1 -0
- package/dist/adapters/langchain.d.cts +6 -0
- package/dist/adapters/langchain.d.ts +6 -0
- package/dist/adapters/langchain.js +312 -0
- package/dist/adapters/langchain.js.map +1 -0
- package/dist/adapters/vercel.cjs +321 -0
- package/dist/adapters/vercel.cjs.map +1 -0
- package/dist/adapters/vercel.d.cts +1 -0
- package/dist/adapters/vercel.d.ts +1 -0
- package/dist/adapters/vercel.js +294 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/firewall-DfZCCDU6.d.cts +197 -0
- package/dist/firewall-DfZCCDU6.d.ts +197 -0
- package/dist/index.cjs +257 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -146
- package/dist/index.d.ts +16 -146
- package/dist/index.js +244 -89
- package/dist/index.js.map +1 -1
- package/package.json +38 -10
package/dist/index.cjs
CHANGED
|
@@ -47,30 +47,39 @@ function parseErrorBody(body) {
|
|
|
47
47
|
return { details: void 0, error: void 0, apiMessage: void 0 };
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
var MAX_PROMPT_DISPLAY_LEN, PromptBlockedException, SilmarilApiError;
|
|
50
|
+
var MAX_PROMPT_DISPLAY_LEN, FirewallBlockedException, PromptBlockedException, SilmarilApiError;
|
|
51
51
|
var init_exceptions = __esm({
|
|
52
52
|
"src/exceptions.ts"() {
|
|
53
53
|
"use strict";
|
|
54
54
|
MAX_PROMPT_DISPLAY_LEN = 100;
|
|
55
|
-
|
|
55
|
+
FirewallBlockedException = class _FirewallBlockedException extends Error {
|
|
56
56
|
score;
|
|
57
57
|
threshold;
|
|
58
58
|
promptText;
|
|
59
59
|
runId;
|
|
60
|
+
hook;
|
|
61
|
+
toolName;
|
|
62
|
+
toolCallId;
|
|
63
|
+
result;
|
|
60
64
|
constructor(params) {
|
|
61
|
-
super(
|
|
62
|
-
this.name = "
|
|
65
|
+
super(_FirewallBlockedException.formatMessage(params));
|
|
66
|
+
this.name = "FirewallBlockedException";
|
|
63
67
|
this.score = params.score;
|
|
64
68
|
this.threshold = params.threshold;
|
|
65
69
|
this.promptText = params.promptText;
|
|
66
70
|
this.runId = params.runId;
|
|
67
|
-
|
|
71
|
+
this.hook = params.hook;
|
|
72
|
+
this.toolName = params.toolName;
|
|
73
|
+
this.toolCallId = params.toolCallId;
|
|
74
|
+
this.result = params.result;
|
|
75
|
+
Object.setPrototypeOf(this, _FirewallBlockedException.prototype);
|
|
68
76
|
}
|
|
69
77
|
static formatMessage(params) {
|
|
70
78
|
const truncated = params.promptText.length > MAX_PROMPT_DISPLAY_LEN ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...` : params.promptText;
|
|
71
|
-
return `
|
|
79
|
+
return `Request blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
|
|
72
80
|
}
|
|
73
81
|
};
|
|
82
|
+
PromptBlockedException = FirewallBlockedException;
|
|
74
83
|
SilmarilApiError = class _SilmarilApiError extends Error {
|
|
75
84
|
status;
|
|
76
85
|
statusText;
|
|
@@ -261,19 +270,12 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
261
270
|
});
|
|
262
271
|
const shadowMode = options.shadowMode ?? firewall.shadowMode;
|
|
263
272
|
const onClassify = options.onClassify;
|
|
264
|
-
const fireOnClassify = (
|
|
273
|
+
const fireOnClassify = (event) => {
|
|
265
274
|
if (!onClassify) {
|
|
266
275
|
return;
|
|
267
276
|
}
|
|
268
277
|
try {
|
|
269
|
-
onClassify(
|
|
270
|
-
hook: hookLabel,
|
|
271
|
-
...toolName !== void 0 ? { toolName } : {},
|
|
272
|
-
text,
|
|
273
|
-
result,
|
|
274
|
-
blocked,
|
|
275
|
-
shadowMode
|
|
276
|
-
});
|
|
278
|
+
onClassify(event);
|
|
277
279
|
} catch (err) {
|
|
278
280
|
logger("onClassify callback threw", err);
|
|
279
281
|
}
|
|
@@ -294,15 +296,30 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
294
296
|
}
|
|
295
297
|
const threshold = result.threshold;
|
|
296
298
|
const blocked = result.score >= threshold;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
const commonEventFields = {
|
|
300
|
+
hook: hookLabel,
|
|
301
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
302
|
+
runId,
|
|
303
|
+
text,
|
|
304
|
+
result
|
|
305
|
+
};
|
|
306
|
+
fireOnClassify({
|
|
307
|
+
...commonEventFields,
|
|
308
|
+
blocked,
|
|
309
|
+
shadowMode
|
|
310
|
+
});
|
|
311
|
+
if (!blocked || shadowMode) {
|
|
312
|
+
return;
|
|
305
313
|
}
|
|
314
|
+
throw new FirewallBlockedException({
|
|
315
|
+
score: result.score,
|
|
316
|
+
threshold,
|
|
317
|
+
promptText: text,
|
|
318
|
+
runId,
|
|
319
|
+
hook: hookLabel,
|
|
320
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
321
|
+
result
|
|
322
|
+
});
|
|
306
323
|
};
|
|
307
324
|
class SilmarilFirewallHandler extends BaseCallbackHandler {
|
|
308
325
|
name = "silmaril_firewall_handler";
|
|
@@ -406,8 +423,8 @@ var init_langchain = __esm({
|
|
|
406
423
|
});
|
|
407
424
|
|
|
408
425
|
// src/index.ts
|
|
409
|
-
var
|
|
410
|
-
__export(
|
|
426
|
+
var src_exports = {};
|
|
427
|
+
__export(src_exports, {
|
|
411
428
|
ALL_HOOKS: () => ALL_HOOKS,
|
|
412
429
|
CHUNK_OVERLAP: () => CHUNK_OVERLAP,
|
|
413
430
|
CHUNK_OVERLAP_CHARS: () => CHUNK_OVERLAP_CHARS,
|
|
@@ -418,21 +435,34 @@ __export(index_exports, {
|
|
|
418
435
|
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
419
436
|
FIREWALL_HOOK_TO_LABEL: () => FIREWALL_HOOK_TO_LABEL,
|
|
420
437
|
Firewall: () => Firewall,
|
|
438
|
+
FirewallBlockedException: () => FirewallBlockedException,
|
|
421
439
|
FirewallHook: () => FirewallHook,
|
|
440
|
+
HARMFUL_OUTCOMES: () => HARMFUL_OUTCOMES,
|
|
422
441
|
HookLabel: () => HookLabel,
|
|
423
442
|
INPUT_HOOKS: () => INPUT_HOOKS,
|
|
424
443
|
MAX_INPUT_CHARS: () => MAX_INPUT_CHARS,
|
|
425
444
|
MAX_INPUT_TOKENS: () => MAX_INPUT_TOKENS,
|
|
445
|
+
OUTCOME_DESCRIPTIONS: () => OUTCOME_DESCRIPTIONS,
|
|
426
446
|
OUTPUT_HOOKS: () => OUTPUT_HOOKS,
|
|
447
|
+
Outcome: () => Outcome,
|
|
448
|
+
PRIMARY_OUTCOMES: () => PRIMARY_OUTCOMES,
|
|
427
449
|
PromptBlockedException: () => PromptBlockedException,
|
|
428
450
|
SilmarilApiError: () => SilmarilApiError,
|
|
429
451
|
chunkText: () => chunkText,
|
|
452
|
+
isHarmfulOutcome: () => isHarmfulOutcome,
|
|
453
|
+
isPrimaryOutcome: () => isPrimaryOutcome,
|
|
454
|
+
normalizeHarmfulOutcome: () => normalizeHarmfulOutcome,
|
|
455
|
+
normalizeHarmfulOutcomeMap: () => normalizeHarmfulOutcomeMap,
|
|
456
|
+
normalizePrimaryOutcome: () => normalizePrimaryOutcome,
|
|
430
457
|
prependHook: () => prependHook,
|
|
431
458
|
prependToolName: () => prependToolName,
|
|
432
459
|
resolveHooks: () => resolveHooks,
|
|
433
460
|
sanitizeText: () => sanitizeText
|
|
434
461
|
});
|
|
435
|
-
module.exports = __toCommonJS(
|
|
462
|
+
module.exports = __toCommonJS(src_exports);
|
|
463
|
+
|
|
464
|
+
// src/firewall.ts
|
|
465
|
+
var import_node_crypto = require("crypto");
|
|
436
466
|
|
|
437
467
|
// src/adapters/vercel.ts
|
|
438
468
|
init_exceptions();
|
|
@@ -450,6 +480,9 @@ function stringifyToolValue(value) {
|
|
|
450
480
|
return String(value);
|
|
451
481
|
}
|
|
452
482
|
}
|
|
483
|
+
function isRecord(value) {
|
|
484
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
485
|
+
}
|
|
453
486
|
function extractContentText(content) {
|
|
454
487
|
if (typeof content === "string") {
|
|
455
488
|
return content;
|
|
@@ -474,14 +507,29 @@ function iterateToolResultParts(message) {
|
|
|
474
507
|
continue;
|
|
475
508
|
}
|
|
476
509
|
if (part.type === "tool-result") {
|
|
477
|
-
const text =
|
|
510
|
+
const text = stringifyToolResult(part.result !== void 0 ? part.result : part.output);
|
|
478
511
|
if (text.trim()) {
|
|
479
|
-
out.push({ text, toolName: part.toolName });
|
|
512
|
+
out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });
|
|
480
513
|
}
|
|
481
514
|
}
|
|
482
515
|
}
|
|
483
516
|
return out;
|
|
484
517
|
}
|
|
518
|
+
function stringifyToolResult(value) {
|
|
519
|
+
if (!isRecord(value)) {
|
|
520
|
+
return stringifyToolValue(value);
|
|
521
|
+
}
|
|
522
|
+
if ((value.type === "text" || value.type === "error-text") && typeof value.value === "string") {
|
|
523
|
+
return value.value;
|
|
524
|
+
}
|
|
525
|
+
if ((value.type === "json" || value.type === "error-json") && value.value !== void 0) {
|
|
526
|
+
return stringifyToolValue(value.value);
|
|
527
|
+
}
|
|
528
|
+
if (value.type === "content" && Array.isArray(value.value)) {
|
|
529
|
+
return value.value.map((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter((text) => text.length > 0).join(" ");
|
|
530
|
+
}
|
|
531
|
+
return stringifyToolValue(value);
|
|
532
|
+
}
|
|
485
533
|
function findLastUserMessage(messages) {
|
|
486
534
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
487
535
|
const msg = messages[i];
|
|
@@ -495,33 +543,43 @@ function createMiddleware(firewall, options = {}) {
|
|
|
495
543
|
const scanInput = options.scanInput ?? true;
|
|
496
544
|
const scanOutput = options.scanOutput ?? false;
|
|
497
545
|
const shadowMode = options.shadowMode ?? firewall.shadowMode;
|
|
498
|
-
const classifyOrBlock = async (text, hook, toolName) => {
|
|
546
|
+
const classifyOrBlock = async (text, hook, context = { toolName: void 0, toolCallId: void 0 }) => {
|
|
499
547
|
if (!text.trim()) {
|
|
500
548
|
return;
|
|
501
549
|
}
|
|
550
|
+
const { toolName, toolCallId } = context;
|
|
502
551
|
const result = await firewall.classify(
|
|
503
552
|
text,
|
|
504
553
|
toolName !== void 0 ? { hook, toolName } : { hook }
|
|
505
554
|
);
|
|
506
555
|
const threshold = result.threshold;
|
|
507
556
|
const blocked = result.score >= threshold;
|
|
508
|
-
|
|
557
|
+
const commonEventFields = {
|
|
509
558
|
hook,
|
|
510
559
|
...toolName !== void 0 ? { toolName } : {},
|
|
560
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
511
561
|
text,
|
|
512
|
-
result
|
|
562
|
+
result
|
|
563
|
+
};
|
|
564
|
+
options.onClassify?.({
|
|
565
|
+
...commonEventFields,
|
|
513
566
|
blocked,
|
|
514
567
|
shadowMode
|
|
515
568
|
});
|
|
516
|
-
if (blocked
|
|
517
|
-
|
|
518
|
-
score: result.score,
|
|
519
|
-
threshold,
|
|
520
|
-
promptText: text
|
|
521
|
-
});
|
|
522
|
-
options.onBlocked?.(err);
|
|
523
|
-
throw err;
|
|
569
|
+
if (!blocked || shadowMode) {
|
|
570
|
+
return;
|
|
524
571
|
}
|
|
572
|
+
const err = new FirewallBlockedException({
|
|
573
|
+
score: result.score,
|
|
574
|
+
threshold,
|
|
575
|
+
promptText: text,
|
|
576
|
+
hook,
|
|
577
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
578
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
579
|
+
result
|
|
580
|
+
});
|
|
581
|
+
options.onBlocked?.(err);
|
|
582
|
+
throw err;
|
|
525
583
|
};
|
|
526
584
|
const scanPrompt = async (prompt) => {
|
|
527
585
|
if (prompt.length === 0) {
|
|
@@ -530,8 +588,8 @@ function createMiddleware(firewall, options = {}) {
|
|
|
530
588
|
const last = prompt[prompt.length - 1];
|
|
531
589
|
const role = last.role.toLowerCase();
|
|
532
590
|
if (role === "tool") {
|
|
533
|
-
for (const { text: text2, toolName } of iterateToolResultParts(last)) {
|
|
534
|
-
await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, toolName);
|
|
591
|
+
for (const { text: text2, toolName, toolCallId } of iterateToolResultParts(last)) {
|
|
592
|
+
await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });
|
|
535
593
|
}
|
|
536
594
|
return;
|
|
537
595
|
}
|
|
@@ -554,21 +612,25 @@ function createMiddleware(firewall, options = {}) {
|
|
|
554
612
|
if (!call || typeof call !== "object") {
|
|
555
613
|
continue;
|
|
556
614
|
}
|
|
557
|
-
const
|
|
615
|
+
const toolInput = call.args !== void 0 ? call.args : call.input;
|
|
616
|
+
const args = typeof toolInput === "string" ? toolInput : stringifyToolValue(toolInput);
|
|
558
617
|
const toolName = typeof call.toolName === "string" ? call.toolName : void 0;
|
|
618
|
+
const toolCallId = typeof call.toolCallId === "string" ? call.toolCallId : void 0;
|
|
559
619
|
if (args.trim()) {
|
|
560
|
-
await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);
|
|
620
|
+
await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });
|
|
561
621
|
}
|
|
562
622
|
}
|
|
563
623
|
}
|
|
564
624
|
};
|
|
565
625
|
return {
|
|
626
|
+
specificationVersion: "v3",
|
|
627
|
+
middlewareVersion: "v2",
|
|
566
628
|
async wrapGenerate({
|
|
567
629
|
params,
|
|
568
630
|
doGenerate
|
|
569
631
|
}) {
|
|
570
632
|
if (scanInput) {
|
|
571
|
-
await scanPrompt(params.prompt);
|
|
633
|
+
await scanPrompt(params.prompt ?? []);
|
|
572
634
|
}
|
|
573
635
|
const result = await doGenerate();
|
|
574
636
|
await scanGenerateResult(result);
|
|
@@ -579,7 +641,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
579
641
|
doStream
|
|
580
642
|
}) {
|
|
581
643
|
if (scanInput) {
|
|
582
|
-
await scanPrompt(params.prompt);
|
|
644
|
+
await scanPrompt(params.prompt ?? []);
|
|
583
645
|
}
|
|
584
646
|
const { stream, ...rest } = await doStream();
|
|
585
647
|
if (!scanOutput) {
|
|
@@ -603,7 +665,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
603
665
|
try {
|
|
604
666
|
await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);
|
|
605
667
|
} catch (err) {
|
|
606
|
-
if (err instanceof
|
|
668
|
+
if (err instanceof FirewallBlockedException) {
|
|
607
669
|
controller.enqueue({ type: "error", error: err });
|
|
608
670
|
return;
|
|
609
671
|
}
|
|
@@ -691,54 +753,133 @@ function chunkText(text) {
|
|
|
691
753
|
|
|
692
754
|
// src/firewall.ts
|
|
693
755
|
init_exceptions();
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
var
|
|
756
|
+
|
|
757
|
+
// src/outcomes.ts
|
|
758
|
+
var Outcome = {
|
|
759
|
+
Benign: "benign",
|
|
760
|
+
InformationDisclosure: "information_disclosure",
|
|
761
|
+
SecretExposure: "secret_exposure",
|
|
762
|
+
ControlAbuse: "control_abuse",
|
|
763
|
+
SystemCompromise: "system_compromise",
|
|
764
|
+
ServiceDisruption: "service_disruption"
|
|
765
|
+
};
|
|
766
|
+
var PRIMARY_OUTCOMES = [
|
|
767
|
+
Outcome.Benign,
|
|
768
|
+
Outcome.InformationDisclosure,
|
|
769
|
+
Outcome.SecretExposure,
|
|
770
|
+
Outcome.ControlAbuse,
|
|
771
|
+
Outcome.SystemCompromise,
|
|
772
|
+
Outcome.ServiceDisruption
|
|
773
|
+
];
|
|
774
|
+
var HARMFUL_OUTCOMES = [
|
|
775
|
+
Outcome.InformationDisclosure,
|
|
776
|
+
Outcome.SecretExposure,
|
|
777
|
+
Outcome.ControlAbuse,
|
|
778
|
+
Outcome.SystemCompromise,
|
|
779
|
+
Outcome.ServiceDisruption
|
|
780
|
+
];
|
|
781
|
+
var OUTCOME_DESCRIPTIONS = {
|
|
782
|
+
[Outcome.Benign]: "No harmful firewall outcome detected.",
|
|
783
|
+
[Outcome.InformationDisclosure]: "Exposes private data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.",
|
|
784
|
+
[Outcome.SecretExposure]: "Exposes credentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.",
|
|
785
|
+
[Outcome.ControlAbuse]: "Misuses authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.",
|
|
786
|
+
[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."
|
|
788
|
+
};
|
|
789
|
+
var PRIMARY_OUTCOME_SET = new Set(PRIMARY_OUTCOMES);
|
|
790
|
+
var HARMFUL_OUTCOME_SET = new Set(HARMFUL_OUTCOMES);
|
|
791
|
+
function isPrimaryOutcome(value) {
|
|
792
|
+
return typeof value === "string" && PRIMARY_OUTCOME_SET.has(value);
|
|
793
|
+
}
|
|
794
|
+
function isHarmfulOutcome(value) {
|
|
795
|
+
return typeof value === "string" && HARMFUL_OUTCOME_SET.has(value);
|
|
796
|
+
}
|
|
797
|
+
function normalizePrimaryOutcome(value, fieldName = "primary_outcome") {
|
|
798
|
+
if (typeof value !== "string") {
|
|
799
|
+
throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(value)}`);
|
|
800
|
+
}
|
|
801
|
+
return isPrimaryOutcome(value) ? value : value;
|
|
802
|
+
}
|
|
803
|
+
function normalizeHarmfulOutcome(value, fieldName = "outcome") {
|
|
804
|
+
if (typeof value !== "string" || value === Outcome.Benign) {
|
|
805
|
+
throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(value)}`);
|
|
806
|
+
}
|
|
807
|
+
return isHarmfulOutcome(value) ? value : value;
|
|
808
|
+
}
|
|
809
|
+
function normalizeHarmfulOutcomeMap(values, fieldName) {
|
|
810
|
+
if (values === void 0 || values === null) {
|
|
811
|
+
return void 0;
|
|
812
|
+
}
|
|
813
|
+
if (typeof values !== "object" || Array.isArray(values)) {
|
|
814
|
+
throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(values)}`);
|
|
815
|
+
}
|
|
816
|
+
const out = {};
|
|
817
|
+
for (const [key, value] of Object.entries(values)) {
|
|
818
|
+
const normalizedKey = normalizeHarmfulOutcome(key, `${fieldName} key`);
|
|
819
|
+
if (typeof value !== "number") {
|
|
820
|
+
throw new Error(`Firewall: invalid ${fieldName} value for ${JSON.stringify(key)}`);
|
|
821
|
+
}
|
|
822
|
+
out[normalizedKey] = value;
|
|
823
|
+
}
|
|
824
|
+
return Object.freeze(out);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/firewall.ts
|
|
828
|
+
var SDK_VERSION = "0.4.2";
|
|
697
829
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
698
830
|
var DEFAULT_CHUNK_CONCURRENCY = 8;
|
|
699
831
|
var DEFAULT_MAX_RETRIES = 5;
|
|
700
832
|
var MAX_BACKOFF_SECONDS = 30;
|
|
701
833
|
var MAX_ERROR_BODY_BYTES = 1 << 16;
|
|
702
|
-
function
|
|
703
|
-
if (!Number.isInteger(scoringOpportunityCount) || scoringOpportunityCount < 1) {
|
|
704
|
-
throw new Error(
|
|
705
|
-
`Firewall: scoringOpportunityCount must be an integer >= 1, got ${scoringOpportunityCount}`
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
if (scoringOpportunityCount === 1) {
|
|
709
|
-
return BASE_THRESHOLD;
|
|
710
|
-
}
|
|
711
|
-
const targetChunkFpr = 1 - Math.pow(1 - TARGET_SEQUENCE_FPR, 1 / scoringOpportunityCount);
|
|
712
|
-
const oddsRatio = TARGET_SEQUENCE_FPR / targetChunkFpr;
|
|
713
|
-
const rawThreshold = oddsRatio / (1 + oddsRatio);
|
|
714
|
-
return Math.min(rawThreshold, MAX_ADAPTIVE_THRESHOLD);
|
|
715
|
-
}
|
|
716
|
-
function blockResultFromResponse(data, threshold) {
|
|
834
|
+
function blockResultFromResponse(data) {
|
|
717
835
|
const result = {
|
|
718
836
|
prediction: data.prediction,
|
|
719
837
|
score: Number(data.score),
|
|
720
|
-
threshold
|
|
838
|
+
threshold: Number(data.threshold)
|
|
721
839
|
};
|
|
722
840
|
if (data.primary_outcome !== void 0) {
|
|
723
|
-
result.primaryOutcome = data.primary_outcome;
|
|
841
|
+
result.primaryOutcome = normalizePrimaryOutcome(data.primary_outcome);
|
|
724
842
|
}
|
|
725
843
|
if (data.outcome_scores !== void 0) {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
844
|
+
const outcomeScores = normalizeHarmfulOutcomeMap(data.outcome_scores, "outcome_scores");
|
|
845
|
+
if (outcomeScores !== void 0) {
|
|
846
|
+
result.outcomeScores = outcomeScores;
|
|
847
|
+
}
|
|
729
848
|
}
|
|
730
849
|
if (data.detector_scores !== void 0) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
850
|
+
const detectorScores = normalizeHarmfulOutcomeMap(data.detector_scores, "detector_scores");
|
|
851
|
+
if (detectorScores !== void 0) {
|
|
852
|
+
result.detectorScores = detectorScores;
|
|
853
|
+
}
|
|
734
854
|
}
|
|
735
855
|
if (data.detector_counts !== void 0) {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
856
|
+
const detectorCounts = normalizeHarmfulOutcomeMap(data.detector_counts, "detector_counts");
|
|
857
|
+
if (detectorCounts !== void 0) {
|
|
858
|
+
result.detectorCounts = detectorCounts;
|
|
859
|
+
}
|
|
739
860
|
}
|
|
740
861
|
return Object.freeze(result);
|
|
741
862
|
}
|
|
863
|
+
function isRecord2(value) {
|
|
864
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
865
|
+
}
|
|
866
|
+
function withSdkMetadata(metadata, info) {
|
|
867
|
+
const payload = { ...metadata ?? {} };
|
|
868
|
+
const existing = payload.silmaril;
|
|
869
|
+
if (existing !== void 0 && !isRecord2(existing)) {
|
|
870
|
+
throw new Error("Firewall: metadata.silmaril must be an object when provided");
|
|
871
|
+
}
|
|
872
|
+
payload.silmaril = {
|
|
873
|
+
...isRecord2(existing) ? existing : {},
|
|
874
|
+
sdk_language: "typescript",
|
|
875
|
+
sdk_version: SDK_VERSION,
|
|
876
|
+
request_id: info.requestId,
|
|
877
|
+
input_index: info.inputIndex,
|
|
878
|
+
chunk_index: info.chunkIndex,
|
|
879
|
+
chunk_count: info.chunkCount
|
|
880
|
+
};
|
|
881
|
+
return payload;
|
|
882
|
+
}
|
|
742
883
|
async function readCappedErrorBody(response) {
|
|
743
884
|
if (!response.body) {
|
|
744
885
|
return response.text().then((body2) => body2.slice(0, MAX_ERROR_BODY_BYTES)).catch(() => "");
|
|
@@ -835,14 +976,24 @@ var Firewall = class {
|
|
|
835
976
|
}
|
|
836
977
|
async classify(text, options = {}) {
|
|
837
978
|
const chunks = chunkText(text);
|
|
838
|
-
const
|
|
979
|
+
const requestId = options.requestId ?? (0, import_node_crypto.randomUUID)();
|
|
839
980
|
if (chunks.length === 1) {
|
|
840
|
-
return this.classifySingleChunk(chunks[0], options,
|
|
981
|
+
return this.classifySingleChunk(chunks[0], options, {
|
|
982
|
+
requestId,
|
|
983
|
+
inputIndex: 0,
|
|
984
|
+
chunkIndex: 0,
|
|
985
|
+
chunkCount: 1
|
|
986
|
+
});
|
|
841
987
|
}
|
|
842
988
|
const results = await mapWithConcurrency(
|
|
843
989
|
chunks,
|
|
844
990
|
this.chunkConcurrency,
|
|
845
|
-
(chunk) => this.classifySingleChunk(chunk, options,
|
|
991
|
+
(chunk, index) => this.classifySingleChunk(chunk, options, {
|
|
992
|
+
requestId,
|
|
993
|
+
inputIndex: 0,
|
|
994
|
+
chunkIndex: index,
|
|
995
|
+
chunkCount: chunks.length
|
|
996
|
+
})
|
|
846
997
|
);
|
|
847
998
|
return results.reduce((best, r) => r.score > best.score ? r : best);
|
|
848
999
|
}
|
|
@@ -865,10 +1016,9 @@ var Firewall = class {
|
|
|
865
1016
|
`Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
|
|
866
1017
|
);
|
|
867
1018
|
}
|
|
868
|
-
const
|
|
1019
|
+
const requestId = options.requestId ?? (0, import_node_crypto.randomUUID)();
|
|
869
1020
|
const payload = {
|
|
870
|
-
texts: texts.map((text) => sanitizeText(text))
|
|
871
|
-
threshold
|
|
1021
|
+
texts: texts.map((text) => sanitizeText(text))
|
|
872
1022
|
};
|
|
873
1023
|
if (options.hooks && options.hooks.length > 0) {
|
|
874
1024
|
payload.hooks = options.hooks.map((h) => String(h));
|
|
@@ -876,14 +1026,21 @@ var Firewall = class {
|
|
|
876
1026
|
if (options.toolNames && options.toolNames.length > 0) {
|
|
877
1027
|
payload.tool_names = options.toolNames.map((t) => t === void 0 ? null : t);
|
|
878
1028
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1029
|
+
payload.metadata = texts.map(
|
|
1030
|
+
(_, index) => withSdkMetadata(options.metadata?.[index], {
|
|
1031
|
+
requestId,
|
|
1032
|
+
inputIndex: index,
|
|
1033
|
+
chunkIndex: 0,
|
|
1034
|
+
chunkCount: 1
|
|
1035
|
+
})
|
|
1036
|
+
);
|
|
882
1037
|
const data = await this.postWithRetry(payload);
|
|
883
|
-
return data.predictions.map((p) => blockResultFromResponse(p
|
|
1038
|
+
return data.predictions.map((p) => blockResultFromResponse(p));
|
|
884
1039
|
}
|
|
885
1040
|
asLangChainHandler(options = {}) {
|
|
886
|
-
return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
|
|
1041
|
+
return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
|
|
1042
|
+
(m) => m.createLangChainHandler(this, options)
|
|
1043
|
+
);
|
|
887
1044
|
}
|
|
888
1045
|
asMiddleware(options = {}) {
|
|
889
1046
|
return createMiddleware(this, options);
|
|
@@ -913,19 +1070,17 @@ var Firewall = class {
|
|
|
913
1070
|
}
|
|
914
1071
|
throw new Error("Firewall: exhausted retries (unreachable)");
|
|
915
1072
|
}
|
|
916
|
-
async classifySingleChunk(text, options,
|
|
917
|
-
const payload = { text
|
|
1073
|
+
async classifySingleChunk(text, options, metadataInfo) {
|
|
1074
|
+
const payload = { text };
|
|
918
1075
|
if (options.hook !== void 0) {
|
|
919
1076
|
payload.hook = options.hook;
|
|
920
1077
|
}
|
|
921
1078
|
if (options.toolName !== void 0) {
|
|
922
1079
|
payload.tool_name = options.toolName;
|
|
923
1080
|
}
|
|
924
|
-
|
|
925
|
-
payload.metadata = options.metadata;
|
|
926
|
-
}
|
|
1081
|
+
payload.metadata = withSdkMetadata(options.metadata, metadataInfo);
|
|
927
1082
|
const data = await this.postWithRetry(payload);
|
|
928
|
-
return blockResultFromResponse(data
|
|
1083
|
+
return blockResultFromResponse(data);
|
|
929
1084
|
}
|
|
930
1085
|
};
|
|
931
1086
|
|
|
@@ -944,15 +1099,25 @@ init_hooks();
|
|
|
944
1099
|
DEFAULT_TIMEOUT_MS,
|
|
945
1100
|
FIREWALL_HOOK_TO_LABEL,
|
|
946
1101
|
Firewall,
|
|
1102
|
+
FirewallBlockedException,
|
|
947
1103
|
FirewallHook,
|
|
1104
|
+
HARMFUL_OUTCOMES,
|
|
948
1105
|
HookLabel,
|
|
949
1106
|
INPUT_HOOKS,
|
|
950
1107
|
MAX_INPUT_CHARS,
|
|
951
1108
|
MAX_INPUT_TOKENS,
|
|
1109
|
+
OUTCOME_DESCRIPTIONS,
|
|
952
1110
|
OUTPUT_HOOKS,
|
|
1111
|
+
Outcome,
|
|
1112
|
+
PRIMARY_OUTCOMES,
|
|
953
1113
|
PromptBlockedException,
|
|
954
1114
|
SilmarilApiError,
|
|
955
1115
|
chunkText,
|
|
1116
|
+
isHarmfulOutcome,
|
|
1117
|
+
isPrimaryOutcome,
|
|
1118
|
+
normalizeHarmfulOutcome,
|
|
1119
|
+
normalizeHarmfulOutcomeMap,
|
|
1120
|
+
normalizePrimaryOutcome,
|
|
956
1121
|
prependHook,
|
|
957
1122
|
prependToolName,
|
|
958
1123
|
resolveHooks,
|