agent-sanitizer 2.60.0 → 2.60.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.
@@ -27,15 +27,23 @@ import charset from "../../python/agent_sanitizer/data/invisible-charset.json" w
27
27
  // every gap are what bound the match — a `*` gap is never adjacent to another
28
28
  // gap, so there is no ambiguity to backtrack over and no ReDoS. (The class size
29
29
  // is irrelevant to that: a character class matches in O(1) whether it holds one
30
- // member or all 435.)
30
+ // member or all 435.) That argument holds only while no literal is itself a
31
+ // member of the class, so {@link envValueRegex} drops the value's own invisible
32
+ // code points before it splices: `a<ZWSP>a` would otherwise build
33
+ // `a[…]*<ZWSP>[…]*a`, whose two stars share the literal and backtrack
34
+ // quadratically over a long run of ZWSP.
35
+ const ENV_INVIS_CODEPOINTS = [
36
+ ...new Set([...charset.cf_codepoints, ...charset.extra_codepoints]),
37
+ ].sort((a, b) => a - b);
38
+ const ENV_INVIS_SET = new Set(ENV_INVIS_CODEPOINTS);
31
39
  const ENV_INVIS_RUN =
32
40
  "[" +
33
- [...new Set([...charset.cf_codepoints, ...charset.extra_codepoints])]
34
- .sort((a, b) => a - b)
35
- .map((cp) => `\\u{${cp.toString(16)}}`)
36
- .join("") +
41
+ ENV_INVIS_CODEPOINTS.map((cp) => `\\u{${cp.toString(16)}}`).join("") +
37
42
  "]*";
38
43
 
44
+ /** @param {string} ch */
45
+ const escapeLiteral = (ch) => ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
+
39
47
  /**
40
48
  * Regex matching `value` tolerating invisible chars spliced between its
41
49
  * characters (mirrors the engine's env-value regex). Code-point split so
@@ -55,10 +63,15 @@ const ENV_INVIS_RUN =
55
63
  export function envValueRegex(value) {
56
64
  let re = ENV_VALUE_REGEX_CACHE.get(value);
57
65
  if (re === undefined) {
66
+ const visible = [...value].filter(
67
+ (ch) => !ENV_INVIS_SET.has(/** @type {number} */ (ch.codePointAt(0))),
68
+ );
69
+ // A value made only of invisibles has no literal to anchor a gap on: match
70
+ // it exactly rather than build a pattern that is nothing but gaps.
58
71
  re = new RegExp(
59
- [...value]
60
- .map((ch) => ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
61
- .join(ENV_INVIS_RUN),
72
+ visible.length > 0
73
+ ? visible.map(escapeLiteral).join(ENV_INVIS_RUN)
74
+ : [...value].map(escapeLiteral).join(""),
62
75
  "u",
63
76
  );
64
77
  ENV_VALUE_REGEX_CACHE.set(value, re);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sanitizer",
3
- "version": "2.60.0",
3
+ "version": "2.60.1",
4
4
  "description": "Defend an agent against hidden-content injection: strip payload-capable invisible Unicode and ANSI, splice out human-invisible HTML, and flag data-exfil URLs in untrusted text before any model sees it.",
5
5
  "type": "module",
6
6
  "repository": {