@ifc-lite/lists 2.1.0 → 2.1.2

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.
@@ -1,22 +1,42 @@
1
- /**
2
- * Property-set / property NAME matching for list columns and queries.
3
- *
4
- * A name wrapped in slashes (`/Qto_.*BaseQuantities/`, optionally with trailing
5
- * flags such as `/qto_.+/i`) is treated as a regular expression matched against
6
- * the candidate name; anything else is an exact, case-sensitive string match
7
- * (the historical behaviour). This lets one column / query pull a value from
8
- * several property or quantity sets at once — e.g. `NetVolume` from
9
- * `Qto_WallBaseQuantities` AND `Qto_SlabBaseQuantities` — the way Bonsai's
10
- * `/regex/` syntax works (issue #1591). IFC set / property names never contain
11
- * slashes, so the `/.../` form is unambiguous.
12
- */
13
1
  type NameMatcher = (name: string) => boolean;
14
2
  /** True when `pattern` uses the `/regex/` form (a valid slash-delimited literal). */
15
3
  export declare function isNamePattern(pattern: string): boolean;
4
+ /**
5
+ * Returns a human-readable rejection reason when `source` is not safe to
6
+ * compile and run `.test()` with, or `undefined` when it's fine. Pure, never
7
+ * throws — delegates to `@ifc-lite/regex-guard`'s `assertGuardedRegexPattern`
8
+ * so this package shares the same catastrophic-backtracking-shape and
9
+ * length-cap checks as `@ifc-lite/ids`, `@ifc-lite/extensions` and
10
+ * `@ifc-lite/mutations`, instead of carrying its own copy. Exposed so a
11
+ * caller that wants to reject a dangerous pattern WITHOUT triggering
12
+ * `compileNameMatcher`'s throw (e.g. a live "does this pattern look right"
13
+ * preview) can check first.
14
+ */
15
+ export declare function unsafeNamePatternReason(source: string): string | undefined;
16
16
  /**
17
17
  * Compile a name pattern into a predicate. `/body/flags` compiles to a RegExp;
18
18
  * anything else (including a malformed literal, which is logged) becomes an
19
19
  * exact, case-sensitive match.
20
+ *
21
+ * `compileNameMatcher` hands its returned predicate straight to `.test()`
22
+ * against untrusted data: a viewer list column, an SDK `psetName`/`propName`
23
+ * argument, or an LLM/agent-authored sandbox script's own call arguments
24
+ * (`packages/sandbox/src/bridge-query.ts`'s `property` tool forwards its
25
+ * args unmodified into `sdk.property` → `compileNameMatcher`, and the regex
26
+ * compiles and runs on the HOST's main thread, outside the QuickJS sandbox —
27
+ * a pattern the sandboxed script "owns" can hang the real browser tab). For
28
+ * that reason, before compiling a `/regex/` literal's body, this runs it
29
+ * through `@ifc-lite/regex-guard`'s `assertGuardedRegexPattern`, which
30
+ * rejects the well-known catastrophic-backtracking shapes and overlong
31
+ * patterns.
32
+ *
33
+ * Throws a plain `Error` — naming the pattern and the reason — when the body
34
+ * is syntactically valid but rejected by the guard: unlike a malformed
35
+ * literal, there is no safe fallback here, because the whole point of
36
+ * rejecting it is to never call `.test()` with it. A caller that cannot let
37
+ * an exception propagate (the sandbox bridge, in particular) MUST catch
38
+ * this — see the callers' own comments for why a throw is safe to cross
39
+ * that boundary.
20
40
  */
21
41
  export declare function compileNameMatcher(pattern: string): NameMatcher;
22
42
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"name-pattern.d.ts","sourceRoot":"","sources":["../src/name-pattern.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;AAU7C,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAU/D"}
1
+ {"version":3,"file":"name-pattern.d.ts","sourceRoot":"","sources":["../src/name-pattern.ts"],"names":[],"mappings":"AAmBA,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;AAU7C,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAoB/D"}
@@ -1,6 +1,19 @@
1
1
  /* This Source Code Form is subject to the terms of the Mozilla Public
2
2
  * License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * Property-set / property NAME matching for list columns and queries.
6
+ *
7
+ * A name wrapped in slashes (`/Qto_.*BaseQuantities/`, optionally with trailing
8
+ * flags such as `/qto_.+/i`) is treated as a regular expression matched against
9
+ * the candidate name; anything else is an exact, case-sensitive string match
10
+ * (the historical behaviour). This lets one column / query pull a value from
11
+ * several property or quantity sets at once — e.g. `NetVolume` from
12
+ * `Qto_WallBaseQuantities` AND `Qto_SlabBaseQuantities` — the way Bonsai's
13
+ * `/regex/` syntax works (issue #1591). IFC set / property names never contain
14
+ * slashes, so the `/.../` form is unambiguous.
15
+ */
16
+ import { assertGuardedRegexPattern, UnsafeRegexPatternError } from '@ifc-lite/regex-guard';
4
17
  // Compiled matchers are cached by pattern string: `findPropertyEntry` /
5
18
  // `findQuantityEntry` run per row, and the pattern is fixed per column, so
6
19
  // without this a regex column would recompile its RegExp for every element.
@@ -12,16 +25,69 @@ const matcherCache = new Map();
12
25
  export function isNamePattern(pattern) {
13
26
  return parseRegexLiteral(pattern) !== null;
14
27
  }
28
+ /**
29
+ * Returns a human-readable rejection reason when `source` is not safe to
30
+ * compile and run `.test()` with, or `undefined` when it's fine. Pure, never
31
+ * throws — delegates to `@ifc-lite/regex-guard`'s `assertGuardedRegexPattern`
32
+ * so this package shares the same catastrophic-backtracking-shape and
33
+ * length-cap checks as `@ifc-lite/ids`, `@ifc-lite/extensions` and
34
+ * `@ifc-lite/mutations`, instead of carrying its own copy. Exposed so a
35
+ * caller that wants to reject a dangerous pattern WITHOUT triggering
36
+ * `compileNameMatcher`'s throw (e.g. a live "does this pattern look right"
37
+ * preview) can check first.
38
+ */
39
+ export function unsafeNamePatternReason(source) {
40
+ try {
41
+ assertGuardedRegexPattern(source);
42
+ return undefined;
43
+ }
44
+ catch (err) {
45
+ if (err instanceof UnsafeRegexPatternError)
46
+ return err.reason;
47
+ throw err;
48
+ }
49
+ }
15
50
  /**
16
51
  * Compile a name pattern into a predicate. `/body/flags` compiles to a RegExp;
17
52
  * anything else (including a malformed literal, which is logged) becomes an
18
53
  * exact, case-sensitive match.
54
+ *
55
+ * `compileNameMatcher` hands its returned predicate straight to `.test()`
56
+ * against untrusted data: a viewer list column, an SDK `psetName`/`propName`
57
+ * argument, or an LLM/agent-authored sandbox script's own call arguments
58
+ * (`packages/sandbox/src/bridge-query.ts`'s `property` tool forwards its
59
+ * args unmodified into `sdk.property` → `compileNameMatcher`, and the regex
60
+ * compiles and runs on the HOST's main thread, outside the QuickJS sandbox —
61
+ * a pattern the sandboxed script "owns" can hang the real browser tab). For
62
+ * that reason, before compiling a `/regex/` literal's body, this runs it
63
+ * through `@ifc-lite/regex-guard`'s `assertGuardedRegexPattern`, which
64
+ * rejects the well-known catastrophic-backtracking shapes and overlong
65
+ * patterns.
66
+ *
67
+ * Throws a plain `Error` — naming the pattern and the reason — when the body
68
+ * is syntactically valid but rejected by the guard: unlike a malformed
69
+ * literal, there is no safe fallback here, because the whole point of
70
+ * rejecting it is to never call `.test()` with it. A caller that cannot let
71
+ * an exception propagate (the sandbox bridge, in particular) MUST catch
72
+ * this — see the callers' own comments for why a throw is safe to cross
73
+ * that boundary.
19
74
  */
20
75
  export function compileNameMatcher(pattern) {
21
76
  const cached = matcherCache.get(pattern);
22
77
  if (cached)
23
78
  return cached;
24
79
  const re = parseRegexLiteral(pattern);
80
+ if (re) {
81
+ try {
82
+ assertGuardedRegexPattern(re.source);
83
+ }
84
+ catch (err) {
85
+ if (err instanceof UnsafeRegexPatternError) {
86
+ throw new Error(`[lists] rejected name pattern ${JSON.stringify(pattern)}: ${err.reason}`);
87
+ }
88
+ throw err;
89
+ }
90
+ }
25
91
  const matcher = re ? (name) => re.test(name) : (name) => name === pattern;
26
92
  if (matcherCache.size >= CACHE_CAP)
27
93
  matcherCache.clear();
@@ -32,6 +98,11 @@ export function compileNameMatcher(pattern) {
32
98
  * Parse a `/body/flags` regex literal, or return null for a plain name. A
33
99
  * malformed literal is NOT silently swallowed: it's logged and treated as a
34
100
  * plain name (so it matches only itself), keeping behaviour predictable.
101
+ *
102
+ * This only validates SYNTAX (does it compile at all) — `isNamePattern` uses
103
+ * it as-is, unaffected by the ReDoS guard above, because merely
104
+ * *constructing* a RegExp never runs it and so cannot ReDoS; only
105
+ * `compileNameMatcher`'s returned predicate ever calls `.test()`.
35
106
  */
36
107
  function parseRegexLiteral(pattern) {
37
108
  const m = /^\/(.+)\/([a-z]*)$/.exec(pattern);
@@ -1 +1 @@
1
- {"version":3,"file":"name-pattern.js","sourceRoot":"","sources":["../src/name-pattern.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAiB/D,wEAAwE;AACxE,2EAA2E;AAC3E,4EAA4E;AAC5E,+EAA+E;AAC/E,qEAAqE;AACrE,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,YAAY,GAAG,IAAI,GAAG,EAAuB,CAAC;AAEpD,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,OAAO,iBAAiB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,EAAE,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,OAAO,GAAgB,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC;IAEvF,IAAI,YAAY,CAAC,IAAI,IAAI,SAAS;QAAE,YAAY,CAAC,KAAK,EAAE,CAAC;IACzD,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,OAAe;IACxC,MAAM,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,uEAAuE;QACvE,uEAAuE;QACvE,wEAAwE;QACxE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,gCAAgC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACnG,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"name-pattern.js","sourceRoot":"","sources":["../src/name-pattern.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAI3F,wEAAwE;AACxE,2EAA2E;AAC3E,4EAA4E;AAC5E,+EAA+E;AAC/E,qEAAqE;AACrE,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,YAAY,GAAG,IAAI,GAAG,EAAuB,CAAC;AAEpD,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,OAAO,iBAAiB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,IAAI,CAAC;QACH,yBAAyB,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,uBAAuB;YAAE,OAAO,GAAG,CAAC,MAAM,CAAC;QAC9D,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,EAAE,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,EAAE,EAAE,CAAC;QACP,IAAI,CAAC;YACH,yBAAyB,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YAC7F,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAgB,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC;IAEvF,IAAI,YAAY,CAAC,IAAI,IAAI,SAAS;QAAE,YAAY,CAAC,KAAK,EAAE,CAAC;IACzD,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,iBAAiB,CAAC,OAAe;IACxC,MAAM,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,uEAAuE;QACvE,uEAAuE;QACvE,wEAAwE;QACxE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,gCAAgC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACnG,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifc-lite/lists",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Configurable property tables and schedules from IFC data for IFC-Lite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,8 +13,9 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "@ifc-lite/data": "4.0.0",
17
- "@ifc-lite/encoding": "2.2.0"
16
+ "@ifc-lite/encoding": "2.2.0",
17
+ "@ifc-lite/data": "4.2.0",
18
+ "@ifc-lite/regex-guard": "0.2.0"
18
19
  },
19
20
  "devDependencies": {
20
21
  "csv-parse": "7.0.2",