@aexhq/cli 0.20.0 → 0.21.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/cli.mjs CHANGED
@@ -838,7 +838,7 @@ function visitCustodyValue(input, path, findings) {
838
838
  }
839
839
  function scanStringValue(value, path, findings) {
840
840
  for (const pattern of forbiddenStringPatterns) {
841
- if (pattern.regex.test(value)) {
841
+ if (matchesForbiddenPattern(pattern, value)) {
842
842
  findings.push(Object.freeze({
843
843
  path,
844
844
  reason: pattern.reason,
@@ -847,11 +847,32 @@ function scanStringValue(value, path, findings) {
847
847
  }
848
848
  }
849
849
  }
850
+ function matchesForbiddenPattern(pattern, value) {
851
+ if (!pattern.accept) {
852
+ return pattern.regex.test(value);
853
+ }
854
+ const scan = pattern.regex.global ? pattern.regex : new RegExp(pattern.regex.source, `${pattern.regex.flags}g`);
855
+ scan.lastIndex = 0;
856
+ let match;
857
+ while ((match = scan.exec(value)) !== null) {
858
+ if (pattern.accept(match[0])) {
859
+ return true;
860
+ }
861
+ if (match.index === scan.lastIndex) {
862
+ scan.lastIndex++;
863
+ }
864
+ }
865
+ return false;
866
+ }
850
867
  var forbiddenStringPatterns = Object.freeze([
851
868
  { reason: "bearer_token", regex: /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/i },
852
869
  {
853
870
  reason: "provider_key",
854
- regex: /\b(?:sk-(?:ant|proj|live|test|deepseek|openai)|xox[baprs]-|AIza)[A-Za-z0-9_-]{8,}/i
871
+ // Prefixed provider keys (`sk-…`, Slack `xox*-…`, Google `AIza…`). The bare
872
+ // `sk-` body is intentionally generic so an unrecognised vendor's `sk-` key
873
+ // (e.g. DeepSeek `sk-<hex>`, OpenRouter `sk-or-…`) is still caught by shape,
874
+ // not left to the narrowed entropy catch-all below.
875
+ regex: /\b(?:sk-[A-Za-z0-9_-]{16,}|xox[baprs]-[A-Za-z0-9-]{8,}|AIza[A-Za-z0-9_-]{8,})/i
855
876
  },
856
877
  { reason: "signed_url", regex: /[?&](?:X-Amz-Signature|X-Amz-Credential|X-Amz-Algorithm|AWSAccessKeyId)=/i },
857
878
  { reason: "object_store_key", regex: /(^|[\s"'`])(?:runs|assets)\/[^?<#\s"'`]+/i },
@@ -860,8 +881,59 @@ var forbiddenStringPatterns = Object.freeze([
860
881
  reason: "private_resource_handle",
861
882
  regex: /\b(?:machine|session|agent|file|skill|env|resource|handle|token_hash|bearer_hash)[_:-][A-Za-z0-9][A-Za-z0-9_-]{7,}\b/i
862
883
  },
863
- { reason: "high_entropy_token", regex: /\b(?=[A-Za-z0-9_-]{40,}\b)(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9_-]{40,}\b/ }
884
+ {
885
+ reason: "high_entropy_token",
886
+ // Catch-all for an unrecognised opaque secret blob. The candidate run
887
+ // EXCLUDES `_` (so `SCREAMING_SNAKE` env names and `slug_with_words` URL
888
+ // segments split instead of fusing into a phantom 40-char run — the live
889
+ // false positive was `ted_season_2_peacock_official_discussion_thread` in
890
+ // web-search result text and a fetched URL), and the `accept` predicate
891
+ // vetoes content-addressed hashes (md5/sha1/sha256 digests — the platform's
892
+ // OWN asset filenames) and low-entropy / single-class runs so only genuine
893
+ // opaque secrets remain. Slash-bearing secrets (signed URLs, connection
894
+ // strings, `Bearer …`) are covered by the named patterns above.
895
+ regex: /\b[A-Za-z0-9-]{40,}\b/,
896
+ accept: isHighEntropySecretRun
897
+ }
864
898
  ]);
899
+ var CONTENT_HASH_RUN = /^(?:[0-9a-f]{32}|[0-9a-f]{40}|[0-9a-f]{64})$/i;
900
+ function isHighEntropySecretRun(run) {
901
+ if (CONTENT_HASH_RUN.test(run)) {
902
+ return false;
903
+ }
904
+ if (!/[A-Za-z]/.test(run) || !/\d/.test(run)) {
905
+ return false;
906
+ }
907
+ if (highEntropyCharClassCount(run) < 2) {
908
+ return false;
909
+ }
910
+ return highEntropyShannonBits(run) >= 3;
911
+ }
912
+ function highEntropyCharClassCount(value) {
913
+ let count = 0;
914
+ if (/[a-z]/.test(value))
915
+ count++;
916
+ if (/[A-Z]/.test(value))
917
+ count++;
918
+ if (/[0-9]/.test(value))
919
+ count++;
920
+ return count;
921
+ }
922
+ function highEntropyShannonBits(value) {
923
+ if (value.length === 0) {
924
+ return 0;
925
+ }
926
+ const counts = /* @__PURE__ */ new Map();
927
+ for (const char of value) {
928
+ counts.set(char, (counts.get(char) ?? 0) + 1);
929
+ }
930
+ let bits = 0;
931
+ for (const count of counts.values()) {
932
+ const p = count / value.length;
933
+ bits -= p * Math.log2(p);
934
+ }
935
+ return bits;
936
+ }
865
937
  function isForbiddenCustodyFieldName(key) {
866
938
  return /^(apiKey|secretValue|bearerHash|signedUrl|objectStoreKey|objectKey|vaultId|providerResponseBody|responseBody|privateResourceHandle|resourceHandle|rawBody)$/i.test(key);
867
939
  }
@@ -1 +1 @@
1
- cbf31722f64f1ca909287456236e1e689291516e227dcdd69935300bbc0994c9 cli.mjs
1
+ d05e5e074b577553d20fa3680f834b7941fe0cb7692c0a743975fcf36c4ec9b5 cli.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexhq/cli",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/aexhq/aex.git"
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "fflate": "^0.8.2",
30
- "@aexhq/contracts": "0.20.0"
30
+ "@aexhq/contracts": "0.21.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "esbuild": "^0.28.0"