@particle-academy/fancy-connector-core 0.10.0 → 0.10.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,26 @@ All notable changes to `fancy-connector-core` are documented here, in
6
6
  **This package is pre-1.0, so breaking changes land in MINOR releases.** The
7
7
  version number is not a promise it can keep yet; the entries below are.
8
8
 
9
+ ## [0.10.1] - 2026-10-10
10
+
11
+ ### Fixed
12
+
13
+ - **`linkRanges`'s trailing-punctuation trim was quadratic (CodeQL
14
+ `js/polynomial-redos`, HIGH).** `match[0].replace(/[.,;:!?)\]}'"]+$/, "")`
15
+ is unanchored at the start, so V8 retries the trailing run from every
16
+ position inside it — measured at 344ms/20k, 1.2s/40k, 5.2s/80k adversarial
17
+ characters, clean quadratic. `linkRanges(text)` runs on arbitrary
18
+ post/message text, not an operator-typed value, so this is reachable from
19
+ untrusted input, not just a slow edge case. Replaced with a backward scan
20
+ over a `Set` of trailing punctuation characters — linear by construction,
21
+ each step only decrements an index. Trim behaviour is unchanged and pinned
22
+ by a 9-case table before the mechanism changed; a growth-ratio test (4x the
23
+ input must cost well under 10x, not the ~18x the old code showed) guards
24
+ against this coming back. PHP's mirror (`Text.php`) uses the same
25
+ character class but was checked directly and does not exhibit the
26
+ blowup — PCRE is linear here, so only the TypeScript side changes. Found
27
+ and measured by Fancy; `CONNECTOR_API_VERSION` is unchanged.
28
+
9
29
  ## [0.10.0] - 2026-09-16
10
30
 
11
31
  Tynn #12 (MOIC's meeting bot): an RFC 5545 invite parser, written from
package/dist/index.cjs CHANGED
@@ -1866,11 +1866,17 @@ function sliceByteRange(text, range) {
1866
1866
  const bytes = encoder.encode(text);
1867
1867
  return new TextDecoder().decode(bytes.slice(range.byteStart, range.byteEnd));
1868
1868
  }
1869
+ var TRAILING_PUNCTUATION = /* @__PURE__ */ new Set([".", ",", ";", ":", "!", "?", ")", "]", "}", "'", '"']);
1870
+ function trimTrailingPunctuation(raw) {
1871
+ let end = raw.length;
1872
+ while (end > 0 && TRAILING_PUNCTUATION.has(raw[end - 1])) end -= 1;
1873
+ return raw.slice(0, end);
1874
+ }
1869
1875
  function linkRanges(text) {
1870
1876
  const found = [];
1871
1877
  for (const match of text.matchAll(/https?:\/\/[^\s]+/g)) {
1872
1878
  if (match.index === void 0) continue;
1873
- const url = match[0].replace(/[.,;:!?)\]}'"]+$/, "");
1879
+ const url = trimTrailingPunctuation(match[0]);
1874
1880
  found.push({ ...byteRangeOf(text, match.index, url), url });
1875
1881
  }
1876
1882
  return found;