@silmaril-security/sdk 0.4.1 → 0.5.0

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
@@ -1,7 +1,12 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res) => function __init() {
4
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
3
+ var __esm = (fn, res, err) => function __init() {
4
+ if (err) throw err[0];
5
+ try {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ } catch (e) {
8
+ throw err = [e], e;
9
+ }
5
10
  };
6
11
  var __export = (target, all) => {
7
12
  for (var name in all)
@@ -273,7 +278,7 @@ async function createLangChainHandler(firewall, options = {}) {
273
278
  return;
274
279
  }
275
280
  const threshold = result.threshold;
276
- const blocked = result.score >= threshold;
281
+ const blocked = result.prediction === "MALICIOUS";
277
282
  const commonEventFields = {
278
283
  hook: hookLabel,
279
284
  ...toolName !== void 0 ? { toolName } : {},
@@ -492,7 +497,7 @@ function createMiddleware(firewall, options = {}) {
492
497
  toolName !== void 0 ? { hook, toolName } : { hook }
493
498
  );
494
499
  const threshold = result.threshold;
495
- const blocked = result.score >= threshold;
500
+ const blocked = result.prediction === "MALICIOUS";
496
501
  const commonEventFields = {
497
502
  hook,
498
503
  ...toolName !== void 0 ? { toolName } : {},
@@ -618,15 +623,80 @@ function createMiddleware(firewall, options = {}) {
618
623
  };
619
624
  }
620
625
 
621
- // src/chunking.ts
622
- var CHARS_PER_TOKEN = 4;
623
- var MAX_INPUT_TOKENS = 81920;
624
- var CHUNK_WINDOW = 400;
625
- var CHUNK_OVERLAP = 64;
626
- var MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;
627
- var CHUNK_WINDOW_CHARS = CHUNK_WINDOW * CHARS_PER_TOKEN;
628
- var CHUNK_OVERLAP_CHARS = CHUNK_OVERLAP * CHARS_PER_TOKEN;
629
- var CHUNK_STRIDE_CHARS = CHUNK_WINDOW_CHARS - CHUNK_OVERLAP_CHARS;
626
+ // src/firewall.ts
627
+ init_exceptions();
628
+
629
+ // src/outcomes.ts
630
+ var Outcome = {
631
+ Benign: "benign",
632
+ InformationDisclosure: "information_disclosure",
633
+ SecretExposure: "secret_exposure",
634
+ ControlAbuse: "control_abuse",
635
+ SystemCompromise: "system_compromise",
636
+ ServiceDisruption: "service_disruption"
637
+ };
638
+ var PRIMARY_OUTCOMES = [
639
+ Outcome.Benign,
640
+ Outcome.InformationDisclosure,
641
+ Outcome.SecretExposure,
642
+ Outcome.ControlAbuse,
643
+ Outcome.SystemCompromise,
644
+ Outcome.ServiceDisruption
645
+ ];
646
+ var HARMFUL_OUTCOMES = [
647
+ Outcome.InformationDisclosure,
648
+ Outcome.SecretExposure,
649
+ Outcome.ControlAbuse,
650
+ Outcome.SystemCompromise,
651
+ Outcome.ServiceDisruption
652
+ ];
653
+ var OUTCOME_DESCRIPTIONS = {
654
+ [Outcome.Benign]: "No harmful firewall outcome detected.",
655
+ [Outcome.InformationDisclosure]: "Exposes private data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.",
656
+ [Outcome.SecretExposure]: "Exposes credentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.",
657
+ [Outcome.ControlAbuse]: "Misuses authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.",
658
+ [Outcome.SystemCompromise]: "Enables privilege escalation, account takeover, hostile integration or plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.",
659
+ [Outcome.ServiceDisruption]: "Causes downtime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence."
660
+ };
661
+ var PRIMARY_OUTCOME_SET = new Set(PRIMARY_OUTCOMES);
662
+ var HARMFUL_OUTCOME_SET = new Set(HARMFUL_OUTCOMES);
663
+ function isPrimaryOutcome(value) {
664
+ return typeof value === "string" && PRIMARY_OUTCOME_SET.has(value);
665
+ }
666
+ function isHarmfulOutcome(value) {
667
+ return typeof value === "string" && HARMFUL_OUTCOME_SET.has(value);
668
+ }
669
+ function normalizePrimaryOutcome(value, fieldName = "primary_outcome") {
670
+ if (typeof value !== "string") {
671
+ throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(value)}`);
672
+ }
673
+ return isPrimaryOutcome(value) ? value : value;
674
+ }
675
+ function normalizeHarmfulOutcome(value, fieldName = "outcome") {
676
+ if (typeof value !== "string" || value === Outcome.Benign) {
677
+ throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(value)}`);
678
+ }
679
+ return isHarmfulOutcome(value) ? value : value;
680
+ }
681
+ function normalizeHarmfulOutcomeMap(values, fieldName) {
682
+ if (values === void 0 || values === null) {
683
+ return void 0;
684
+ }
685
+ if (typeof values !== "object" || Array.isArray(values)) {
686
+ throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(values)}`);
687
+ }
688
+ const out = {};
689
+ for (const [key, value] of Object.entries(values)) {
690
+ const normalizedKey = normalizeHarmfulOutcome(key, `${fieldName} key`);
691
+ if (typeof value !== "number") {
692
+ throw new Error(`Firewall: invalid ${fieldName} value for ${JSON.stringify(key)}`);
693
+ }
694
+ out[normalizedKey] = value;
695
+ }
696
+ return Object.freeze(out);
697
+ }
698
+
699
+ // src/sanitization.ts
630
700
  function isHighSurrogate(code) {
631
701
  return code >= 55296 && code <= 56319;
632
702
  }
@@ -645,82 +715,48 @@ function sanitizeText(text) {
645
715
  }
646
716
  continue;
647
717
  }
648
- if (isLowSurrogate(code)) {
649
- continue;
718
+ if (!isLowSurrogate(code)) {
719
+ out += text[i];
650
720
  }
651
- out += text[i];
652
721
  }
653
722
  return out;
654
723
  }
655
- function safeChunkStart(text, start) {
656
- if (start > 0 && start < text.length && isLowSurrogate(text.charCodeAt(start)) && isHighSurrogate(text.charCodeAt(start - 1))) {
657
- return start + 1;
658
- }
659
- return start;
660
- }
661
- function safeChunkEnd(text, end) {
662
- if (end > 0 && end < text.length && isHighSurrogate(text.charCodeAt(end - 1)) && isLowSurrogate(text.charCodeAt(end))) {
663
- return end - 1;
664
- }
665
- return end;
666
- }
667
- function chunkText(text) {
668
- text = sanitizeText(text);
669
- const n = text.length;
670
- if (n > MAX_INPUT_CHARS) {
671
- throw new Error(
672
- `Input has ~${Math.floor(n / CHARS_PER_TOKEN)} tokens (${n} chars); max is ${MAX_INPUT_TOKENS} tokens (${MAX_INPUT_CHARS} chars)`
673
- );
674
- }
675
- if (n <= CHUNK_WINDOW_CHARS) {
676
- return [text];
677
- }
678
- const chunks = [];
679
- for (let start = 0; start < n; start += CHUNK_STRIDE_CHARS) {
680
- const safeStart = safeChunkStart(text, start);
681
- if (safeStart >= n) {
682
- break;
683
- }
684
- const end = safeChunkEnd(text, safeStart + CHUNK_WINDOW_CHARS);
685
- chunks.push(text.slice(safeStart, end));
686
- if (end >= n) {
687
- break;
688
- }
689
- }
690
- return chunks;
691
- }
692
724
 
693
725
  // src/firewall.ts
694
- init_exceptions();
695
- var SDK_VERSION = "0.4.1";
726
+ var SDK_VERSION = "0.5.0";
696
727
  var DEFAULT_TIMEOUT_MS = 1e4;
697
- var DEFAULT_CHUNK_CONCURRENCY = 8;
698
728
  var DEFAULT_MAX_RETRIES = 5;
699
729
  var MAX_BACKOFF_SECONDS = 30;
700
730
  var MAX_ERROR_BODY_BYTES = 1 << 16;
701
731
  function blockResultFromResponse(data) {
732
+ if (data.prediction !== "BENIGN" && data.prediction !== "MALICIOUS") {
733
+ throw new Error("Firewall: response prediction must be BENIGN or MALICIOUS");
734
+ }
702
735
  const result = {
703
736
  prediction: data.prediction,
704
737
  score: Number(data.score),
705
738
  threshold: Number(data.threshold)
706
739
  };
707
740
  if (data.primary_outcome !== void 0) {
708
- result.primaryOutcome = data.primary_outcome;
741
+ result.primaryOutcome = normalizePrimaryOutcome(data.primary_outcome);
709
742
  }
710
743
  if (data.outcome_scores !== void 0) {
711
- result.outcomeScores = Object.freeze(
712
- Object.fromEntries(Object.entries(data.outcome_scores).map(([k, v]) => [k, Number(v)]))
713
- );
744
+ const outcomeScores = normalizeHarmfulOutcomeMap(data.outcome_scores, "outcome_scores");
745
+ if (outcomeScores !== void 0) {
746
+ result.outcomeScores = outcomeScores;
747
+ }
714
748
  }
715
749
  if (data.detector_scores !== void 0) {
716
- result.detectorScores = Object.freeze(
717
- Object.fromEntries(Object.entries(data.detector_scores).map(([k, v]) => [k, Number(v)]))
718
- );
750
+ const detectorScores = normalizeHarmfulOutcomeMap(data.detector_scores, "detector_scores");
751
+ if (detectorScores !== void 0) {
752
+ result.detectorScores = detectorScores;
753
+ }
719
754
  }
720
755
  if (data.detector_counts !== void 0) {
721
- result.detectorCounts = Object.freeze(
722
- Object.fromEntries(Object.entries(data.detector_counts).map(([k, v]) => [k, Number(v)]))
723
- );
756
+ const detectorCounts = normalizeHarmfulOutcomeMap(data.detector_counts, "detector_counts");
757
+ if (detectorCounts !== void 0) {
758
+ result.detectorCounts = detectorCounts;
759
+ }
724
760
  }
725
761
  return Object.freeze(result);
726
762
  }
@@ -738,9 +774,7 @@ function withSdkMetadata(metadata, info) {
738
774
  sdk_language: "typescript",
739
775
  sdk_version: SDK_VERSION,
740
776
  request_id: info.requestId,
741
- input_index: info.inputIndex,
742
- chunk_index: info.chunkIndex,
743
- chunk_count: info.chunkCount
777
+ ...info.inputIndex === void 0 ? {} : { input_index: info.inputIndex }
744
778
  };
745
779
  return payload;
746
780
  }
@@ -781,36 +815,10 @@ async function readCappedErrorBody(response) {
781
815
  }
782
816
  return new TextDecoder().decode(body);
783
817
  }
784
- async function mapWithConcurrency(items, concurrency, task) {
785
- const results = new Array(items.length);
786
- let nextIndex = 0;
787
- let firstError;
788
- const workerCount = Math.min(concurrency, items.length);
789
- async function worker() {
790
- for (; ; ) {
791
- const index = nextIndex;
792
- nextIndex += 1;
793
- if (index >= items.length) {
794
- return;
795
- }
796
- try {
797
- results[index] = await task(items[index], index);
798
- } catch (err) {
799
- firstError ??= err;
800
- }
801
- }
802
- }
803
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
804
- if (firstError !== void 0) {
805
- throw firstError;
806
- }
807
- return results;
808
- }
809
818
  var Firewall = class {
810
819
  apiKey;
811
820
  apiUrl;
812
821
  timeoutMs;
813
- chunkConcurrency;
814
822
  shadowMode;
815
823
  headers;
816
824
  constructor(options) {
@@ -826,12 +834,6 @@ var Firewall = class {
826
834
  if (typeof this.timeoutMs !== "number" || !Number.isFinite(this.timeoutMs) || this.timeoutMs < 0) {
827
835
  throw new Error(`Firewall: timeoutMs must be a finite non-negative number, got ${this.timeoutMs}`);
828
836
  }
829
- this.chunkConcurrency = options.chunkConcurrency ?? DEFAULT_CHUNK_CONCURRENCY;
830
- if (!Number.isInteger(this.chunkConcurrency) || this.chunkConcurrency < 1) {
831
- throw new Error(
832
- `Firewall: chunkConcurrency must be an integer >= 1, got ${this.chunkConcurrency}`
833
- );
834
- }
835
837
  this.shadowMode = options.shadowMode ?? false;
836
838
  this.headers = Object.freeze({
837
839
  "x-api-key": this.apiKey,
@@ -839,27 +841,8 @@ var Firewall = class {
839
841
  });
840
842
  }
841
843
  async classify(text, options = {}) {
842
- const chunks = chunkText(text);
843
844
  const requestId = options.requestId ?? randomUUID();
844
- if (chunks.length === 1) {
845
- return this.classifySingleChunk(chunks[0], options, {
846
- requestId,
847
- inputIndex: 0,
848
- chunkIndex: 0,
849
- chunkCount: 1
850
- });
851
- }
852
- const results = await mapWithConcurrency(
853
- chunks,
854
- this.chunkConcurrency,
855
- (chunk, index) => this.classifySingleChunk(chunk, options, {
856
- requestId,
857
- inputIndex: 0,
858
- chunkIndex: index,
859
- chunkCount: chunks.length
860
- })
861
- );
862
- return results.reduce((best, r) => r.score > best.score ? r : best);
845
+ return this.classifySingle(sanitizeText(text), options, { requestId });
863
846
  }
864
847
  async classifyBatch(texts, options = {}) {
865
848
  if (texts.length === 0) {
@@ -893,9 +876,7 @@ var Firewall = class {
893
876
  payload.metadata = texts.map(
894
877
  (_, index) => withSdkMetadata(options.metadata?.[index], {
895
878
  requestId,
896
- inputIndex: index,
897
- chunkIndex: 0,
898
- chunkCount: 1
879
+ inputIndex: index
899
880
  })
900
881
  );
901
882
  const data = await this.postWithRetry(payload);
@@ -934,7 +915,7 @@ var Firewall = class {
934
915
  }
935
916
  throw new Error("Firewall: exhausted retries (unreachable)");
936
917
  }
937
- async classifySingleChunk(text, options, metadataInfo) {
918
+ async classifySingle(text, options, metadataInfo) {
938
919
  const payload = { text };
939
920
  if (options.hook !== void 0) {
940
921
  payload.hook = options.hook;
@@ -953,28 +934,28 @@ init_exceptions();
953
934
  init_hooks();
954
935
  export {
955
936
  ALL_HOOKS,
956
- CHUNK_OVERLAP,
957
- CHUNK_OVERLAP_CHARS,
958
- CHUNK_WINDOW,
959
- CHUNK_WINDOW_CHARS,
960
- DEFAULT_CHUNK_CONCURRENCY,
961
937
  DEFAULT_HOOKS,
962
938
  DEFAULT_TIMEOUT_MS,
963
939
  FIREWALL_HOOK_TO_LABEL,
964
940
  Firewall,
965
941
  FirewallBlockedException,
966
942
  FirewallHook,
943
+ HARMFUL_OUTCOMES,
967
944
  HookLabel,
968
945
  INPUT_HOOKS,
969
- MAX_INPUT_CHARS,
970
- MAX_INPUT_TOKENS,
946
+ OUTCOME_DESCRIPTIONS,
971
947
  OUTPUT_HOOKS,
948
+ Outcome,
949
+ PRIMARY_OUTCOMES,
972
950
  PromptBlockedException,
973
951
  SilmarilApiError,
974
- chunkText,
952
+ isHarmfulOutcome,
953
+ isPrimaryOutcome,
954
+ normalizeHarmfulOutcome,
955
+ normalizeHarmfulOutcomeMap,
956
+ normalizePrimaryOutcome,
975
957
  prependHook,
976
958
  prependToolName,
977
- resolveHooks,
978
- sanitizeText
959
+ resolveHooks
979
960
  };
980
961
  //# sourceMappingURL=index.js.map