@silmaril-security/sdk 0.7.7 → 0.7.9

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/dist/index.js CHANGED
@@ -14,6 +14,19 @@ var __export = (target, all) => {
14
14
  };
15
15
 
16
16
  // src/exceptions.ts
17
+ function isReviewCompatibilityError(error) {
18
+ if (typeof error !== "object" && typeof error !== "function" || error === null) {
19
+ return false;
20
+ }
21
+ try {
22
+ return Object.getOwnPropertyDescriptor(
23
+ error,
24
+ REVIEW_COMPATIBILITY_ERROR_BRAND
25
+ )?.value === true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
17
30
  function parseErrorBody(body) {
18
31
  try {
19
32
  const parsed = JSON.parse(body);
@@ -30,7 +43,7 @@ function parseErrorBody(body) {
30
43
  return { details: void 0, error: void 0, apiMessage: void 0 };
31
44
  }
32
45
  }
33
- var MAX_PROMPT_DISPLAY_LEN, FirewallBlockedException, PromptBlockedException, SilmarilApiError;
46
+ var MAX_PROMPT_DISPLAY_LEN, FirewallBlockedException, PromptBlockedException, REVIEW_COMPATIBILITY_ERROR_BRAND, ReviewCompatibilityError, SilmarilApiError;
34
47
  var init_exceptions = __esm({
35
48
  "src/exceptions.ts"() {
36
49
  "use strict";
@@ -66,6 +79,27 @@ var init_exceptions = __esm({
66
79
  }
67
80
  };
68
81
  PromptBlockedException = FirewallBlockedException;
82
+ REVIEW_COMPATIBILITY_ERROR_BRAND = /* @__PURE__ */ Symbol.for(
83
+ "@silmaril-security/sdk/ReviewCompatibilityError"
84
+ );
85
+ ReviewCompatibilityError = class _ReviewCompatibilityError extends Error {
86
+ requestedReview;
87
+ acknowledgedReview;
88
+ constructor(requestedReview, acknowledgedReview) {
89
+ const detail = acknowledgedReview === void 0 ? "the response omitted its acknowledgement" : "the response acknowledgement did not match";
90
+ super(`Silmaril review compatibility error: ${detail} for requested ${requestedReview} review`);
91
+ this.name = "ReviewCompatibilityError";
92
+ this.requestedReview = requestedReview;
93
+ this.acknowledgedReview = acknowledgedReview;
94
+ Object.defineProperty(this, REVIEW_COMPATIBILITY_ERROR_BRAND, {
95
+ value: true,
96
+ enumerable: false,
97
+ configurable: false,
98
+ writable: false
99
+ });
100
+ Object.setPrototypeOf(this, _ReviewCompatibilityError.prototype);
101
+ }
102
+ };
69
103
  SilmarilApiError = class _SilmarilApiError extends Error {
70
104
  status;
71
105
  statusText;
@@ -174,6 +208,19 @@ var init_hooks = __esm({
174
208
  }
175
209
  });
176
210
 
211
+ // src/modes.ts
212
+ function validateMode(value) {
213
+ if (value === void 0 || value === "shadow" || value === "warn" || value === "block") {
214
+ return value;
215
+ }
216
+ throw new Error("Firewall: mode must be shadow, warn, or block");
217
+ }
218
+ var init_modes = __esm({
219
+ "src/modes.ts"() {
220
+ "use strict";
221
+ }
222
+ });
223
+
177
224
  // src/utils/extract.ts
178
225
  function extractTextFromPrompts(prompts) {
179
226
  return prompts.map((p) => p.trim()).filter((p) => p.length > 0).join("\n");
@@ -257,7 +304,7 @@ async function createLangChainHandler(firewall, options = {}) {
257
304
  const logger = options.logger ?? ((message, error) => {
258
305
  console.warn(`silmaril.firewall: ${message}`, error);
259
306
  });
260
- const requestedMode = options.mode ?? (options.shadowMode === void 0 ? firewall.mode : options.shadowMode ? "shadow" : "block");
307
+ const requestedMode = validateMode(options.mode) ?? (options.shadowMode === void 0 ? firewall.mode : options.shadowMode ? "shadow" : "block");
261
308
  const onClassify = options.onClassify;
262
309
  const fireOnClassify = (event) => {
263
310
  if (!onClassify) {
@@ -282,6 +329,9 @@ async function createLangChainHandler(firewall, options = {}) {
282
329
  ...toolName !== void 0 ? { toolName } : {}
283
330
  });
284
331
  } catch (err) {
332
+ if (isReviewCompatibilityError(err)) {
333
+ throw err;
334
+ }
285
335
  if (!failOpen) {
286
336
  throw err;
287
337
  }
@@ -413,6 +463,7 @@ var init_langchain = __esm({
413
463
  "use strict";
414
464
  init_exceptions();
415
465
  init_hooks();
466
+ init_modes();
416
467
  init_extract();
417
468
  USER_ROLES = /* @__PURE__ */ new Set(["human", "user"]);
418
469
  }
@@ -424,6 +475,7 @@ import { randomUUID } from "crypto";
424
475
  // src/adapters/vercel.ts
425
476
  init_exceptions();
426
477
  init_hooks();
478
+ init_modes();
427
479
  function stringifyToolValue(value) {
428
480
  if (value === null || value === void 0) {
429
481
  return "";
@@ -499,7 +551,7 @@ function findLastUserMessage(messages) {
499
551
  function createMiddleware(firewall, options = {}) {
500
552
  const scanInput = options.scanInput ?? true;
501
553
  const scanOutput = options.scanOutput ?? false;
502
- const requestedMode = options.mode ?? (options.shadowMode === void 0 ? firewall.mode : options.shadowMode ? "shadow" : "block");
554
+ const requestedMode = validateMode(options.mode) ?? (options.shadowMode === void 0 ? firewall.mode : options.shadowMode ? "shadow" : "block");
503
555
  const classifyOrBlock = async (text, hook, context = { toolName: void 0, toolCallId: void 0 }) => {
504
556
  if (!text.trim()) {
505
557
  return;
@@ -644,6 +696,7 @@ function createMiddleware(firewall, options = {}) {
644
696
 
645
697
  // src/firewall.ts
646
698
  init_exceptions();
699
+ init_modes();
647
700
 
648
701
  // src/outcomes.ts
649
702
  var Outcome = {
@@ -766,12 +819,32 @@ function sanitizeText(text) {
766
819
  }
767
820
 
768
821
  // src/firewall.ts
769
- var SDK_VERSION = "0.7.7";
822
+ var SDK_VERSION = "0.7.9";
770
823
  var DEFAULT_TIMEOUT_MS = 1e4;
771
824
  var DEFAULT_MAX_RETRIES = 5;
772
825
  var MAX_BACKOFF_SECONDS = 30;
773
826
  var MAX_ERROR_BODY_BYTES = 1 << 16;
774
827
  var MAX_TIMEOUT_MS = 2147483647;
828
+ function validateReview(value, name) {
829
+ if (value === void 0) {
830
+ return void 0;
831
+ }
832
+ if (value !== "standard" && value !== "deep") {
833
+ throw new Error(`Firewall: ${name} must be standard or deep`);
834
+ }
835
+ return value;
836
+ }
837
+ function resolveReview(perCallReview, clientReview) {
838
+ return perCallReview === void 0 ? clientReview : validateReview(perCallReview, "review");
839
+ }
840
+ function verifyReviewAcknowledgement(acknowledgedReview, requestedReview) {
841
+ if (requestedReview !== void 0 && acknowledgedReview !== requestedReview) {
842
+ throw new ReviewCompatibilityError(requestedReview, acknowledgedReview);
843
+ }
844
+ }
845
+ function reviewAcknowledgementFromResponse(response) {
846
+ return isRecord2(response) ? response.review : void 0;
847
+ }
775
848
  function resolveMode(value, requestedMode) {
776
849
  let responseMode;
777
850
  if (value === "shadow" || value === "warn" || value === "block") {
@@ -784,7 +857,7 @@ function resolveMode(value, requestedMode) {
784
857
  function legacyMode(shadowMode) {
785
858
  return shadowMode === void 0 ? void 0 : shadowMode ? "shadow" : "block";
786
859
  }
787
- function blockResultFromResponse(data, requestedMode) {
860
+ function blockResultFromResponse(data, requestedMode, requestedReview) {
788
861
  if (data.prediction !== "BENIGN" && data.prediction !== "MALICIOUS") {
789
862
  throw new Error("Firewall: response prediction must be BENIGN or MALICIOUS");
790
863
  }
@@ -793,6 +866,9 @@ function blockResultFromResponse(data, requestedMode) {
793
866
  score: Number(data.score),
794
867
  threshold: Number(data.threshold)
795
868
  };
869
+ if (requestedReview !== void 0) {
870
+ result.review = requestedReview;
871
+ }
796
872
  const mode = resolveMode(data.mode, requestedMode);
797
873
  if (mode !== void 0) {
798
874
  result.mode = mode;
@@ -1067,6 +1143,7 @@ var Firewall = class {
1067
1143
  timeoutMs;
1068
1144
  shadowMode;
1069
1145
  mode;
1146
+ review;
1070
1147
  headers;
1071
1148
  constructor(options) {
1072
1149
  if (!options.apiKey) {
@@ -1086,7 +1163,8 @@ var Firewall = class {
1086
1163
  `Firewall: timeoutMs must be at most ${MAX_TIMEOUT_MS} ms, got ${this.timeoutMs}`
1087
1164
  );
1088
1165
  }
1089
- this.mode = options.mode ?? legacyMode(options.shadowMode);
1166
+ this.mode = validateMode(options.mode) ?? legacyMode(options.shadowMode);
1167
+ this.review = validateReview(options.review, "review");
1090
1168
  this.shadowMode = this.mode === "shadow";
1091
1169
  this.headers = Object.freeze({
1092
1170
  "x-api-key": this.apiKey,
@@ -1125,10 +1203,14 @@ var Firewall = class {
1125
1203
  const payload = {
1126
1204
  texts: texts.map((text) => sanitizeText(text))
1127
1205
  };
1128
- const requestedMode = options.mode ?? this.mode;
1206
+ const requestedMode = validateMode(options.mode) ?? this.mode;
1207
+ const requestedReview = resolveReview(options.review, this.review);
1129
1208
  if (requestedMode !== void 0) {
1130
1209
  payload.mode = requestedMode;
1131
1210
  }
1211
+ if (requestedReview !== void 0) {
1212
+ payload.review = requestedReview;
1213
+ }
1132
1214
  if (options.hooks && options.hooks.length > 0) {
1133
1215
  payload.hooks = options.hooks.map((h) => String(h));
1134
1216
  }
@@ -1148,6 +1230,7 @@ var Firewall = class {
1148
1230
  );
1149
1231
  }
1150
1232
  const data = await this.postWithRetry(payload, options.signal);
1233
+ verifyReviewAcknowledgement(reviewAcknowledgementFromResponse(data), requestedReview);
1151
1234
  if (!Array.isArray(data.predictions)) {
1152
1235
  throw new Error("Firewall: response predictions must be an array");
1153
1236
  }
@@ -1156,7 +1239,9 @@ var Firewall = class {
1156
1239
  `Firewall: response predictions length ${data.predictions.length} does not match submitted texts length ${payload.texts.length}`
1157
1240
  );
1158
1241
  }
1159
- return data.predictions.map((p) => blockResultFromResponse(p, requestedMode));
1242
+ return data.predictions.map(
1243
+ (p) => blockResultFromResponse(p, requestedMode, requestedReview)
1244
+ );
1160
1245
  }
1161
1246
  asLangChainHandler(options = {}) {
1162
1247
  return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
@@ -1205,10 +1290,14 @@ var Firewall = class {
1205
1290
  }
1206
1291
  async classifySingle(text, options, metadataInfo) {
1207
1292
  const payload = { text };
1208
- const requestedMode = options.mode ?? this.mode;
1293
+ const requestedMode = validateMode(options.mode) ?? this.mode;
1294
+ const requestedReview = resolveReview(options.review, this.review);
1209
1295
  if (requestedMode !== void 0) {
1210
1296
  payload.mode = requestedMode;
1211
1297
  }
1298
+ if (requestedReview !== void 0) {
1299
+ payload.review = requestedReview;
1300
+ }
1212
1301
  if (options.hook !== void 0) {
1213
1302
  payload.hook = options.hook;
1214
1303
  }
@@ -1223,7 +1312,8 @@ var Firewall = class {
1223
1312
  ...options.governance === void 0 ? {} : { governance: options.governance }
1224
1313
  });
1225
1314
  const data = await this.postWithRetry(payload, options.signal);
1226
- return blockResultFromResponse(data, requestedMode);
1315
+ verifyReviewAcknowledgement(reviewAcknowledgementFromResponse(data), requestedReview);
1316
+ return blockResultFromResponse(data, requestedMode, requestedReview);
1227
1317
  }
1228
1318
  };
1229
1319
 
@@ -1246,6 +1336,7 @@ export {
1246
1336
  Outcome,
1247
1337
  PRIMARY_OUTCOMES,
1248
1338
  PromptBlockedException,
1339
+ ReviewCompatibilityError,
1249
1340
  SilmarilApiError,
1250
1341
  isHarmfulOutcome,
1251
1342
  isPrimaryOutcome,