@salesforce/ui-design-mode 10.14.1 → 10.15.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.
Files changed (30) hide show
  1. package/dist/authoring/core/editors/ColorEditor.d.cts +4 -0
  2. package/dist/authoring/core/editors/ColorEditor.d.ts +4 -0
  3. package/dist/authoring/core/editors/ColorEditor.d.ts.map +1 -1
  4. package/dist/authoring/core/utils/colorUtils.d.cts +1 -1
  5. package/dist/authoring/core/utils/colorUtils.d.ts +1 -1
  6. package/dist/authoring/core/utils/colorUtils.d.ts.map +1 -1
  7. package/dist/authoring/core/utils/domUtils.d.ts.map +1 -1
  8. package/dist/authoring/react/index.js +3 -3
  9. package/dist/authoring/web-component/index.js +14 -14
  10. package/dist/protocol/messageTypes.d.cts +7 -0
  11. package/dist/protocol/messageTypes.d.ts +7 -0
  12. package/dist/protocol/messageTypes.d.ts.map +1 -1
  13. package/dist/runtime/design-mode-interactions.js +72 -5
  14. package/dist/runtime/interactions/componentMatcher.d.cts +10 -0
  15. package/dist/runtime/interactions/componentMatcher.d.ts +10 -0
  16. package/dist/runtime/interactions/componentMatcher.d.ts.map +1 -1
  17. package/dist/runtime/interactions/interactionsController.d.cts +4 -2
  18. package/dist/runtime/interactions/interactionsController.d.ts +4 -2
  19. package/dist/runtime/interactions/interactionsController.d.ts.map +1 -1
  20. package/dist/security/index.cjs +4 -0
  21. package/dist/security/index.d.cts +17 -0
  22. package/dist/security/index.d.ts +17 -0
  23. package/dist/security/index.d.ts.map +1 -0
  24. package/dist/security/index.js +4 -0
  25. package/dist/security/pathContainment.cjs +37 -0
  26. package/dist/security/pathContainment.d.cts +16 -0
  27. package/dist/security/pathContainment.d.ts +16 -0
  28. package/dist/security/pathContainment.d.ts.map +1 -0
  29. package/dist/security/pathContainment.js +37 -0
  30. package/package.json +11 -1
@@ -29,6 +29,13 @@ export interface SourceLocation {
29
29
  }
30
30
  export interface EnableInteractionsMessage {
31
31
  type: "enable-interactions";
32
+ /**
33
+ * Trusted workspace-folder paths supplied by the host. The runtime refuses to
34
+ * highlight/select an element whose `data-source-file` resolves outside every
35
+ * root. Omitted/empty ⇒ no path gating (the host's file-read sink remains the
36
+ * authoritative boundary).
37
+ */
38
+ workspaceRoots?: string[];
32
39
  }
33
40
  export interface DisableInteractionsMessage {
34
41
  type: "disable-interactions";
@@ -29,6 +29,13 @@ export interface SourceLocation {
29
29
  }
30
30
  export interface EnableInteractionsMessage {
31
31
  type: "enable-interactions";
32
+ /**
33
+ * Trusted workspace-folder paths supplied by the host. The runtime refuses to
34
+ * highlight/select an element whose `data-source-file` resolves outside every
35
+ * root. Omitted/empty ⇒ no path gating (the host's file-read sink remains the
36
+ * authoritative boundary).
37
+ */
38
+ workspaceRoots?: string[];
32
39
  }
33
40
  export interface DisableInteractionsMessage {
34
41
  type: "disable-interactions";
@@ -1 +1 @@
1
- {"version":3,"file":"messageTypes.d.ts","sourceRoot":"","sources":["../../src/protocol/messageTypes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"messageTypes.d.ts","sourceRoot":"","sources":["../../src/protocol/messageTypes.ts"],"names":[],"mappings":"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"}
@@ -370,12 +370,58 @@
370
370
  }
371
371
  };
372
372
 
373
+ // src/security/pathContainment.ts
374
+ var normalizeSeparators = (p) => p.replace(/\\/g, "/");
375
+ var stripTrailingSlash = (p) => {
376
+ const s = p.replace(/\/+$/, "");
377
+ return s === "" && p.startsWith("/") ? "/" : s;
378
+ };
379
+ var normalizeDotSegments = (p) => {
380
+ const normalized = normalizeSeparators(p);
381
+ const isAbsolute = normalized.startsWith("/");
382
+ const out = [];
383
+ for (const seg of normalized.split("/")) {
384
+ if (seg === "" || seg === ".") continue;
385
+ if (seg === "..") {
386
+ if (out.length > 0 && out[out.length - 1] !== "..") out.pop();
387
+ else if (!isAbsolute) out.push("..");
388
+ } else {
389
+ out.push(seg);
390
+ }
391
+ }
392
+ return (isAbsolute ? "/" : "") + out.join("/");
393
+ };
394
+ var isPathContained = (candidate, roots) => {
395
+ if (typeof candidate !== "string" || candidate.trim() === "") return false;
396
+ if (!Array.isArray(roots) || roots.length === 0) return false;
397
+ const normalizedCandidate = stripTrailingSlash(normalizeDotSegments(candidate));
398
+ if (normalizedCandidate === "") return false;
399
+ return roots.some((root) => {
400
+ if (typeof root !== "string" || root.trim() === "") return false;
401
+ const normalizedRoot = stripTrailingSlash(normalizeDotSegments(root));
402
+ if (normalizedRoot === "") return false;
403
+ if (normalizedCandidate === normalizedRoot) return true;
404
+ const prefix = normalizedRoot === "/" ? "/" : `${normalizedRoot}/`;
405
+ return normalizedCandidate.startsWith(prefix);
406
+ });
407
+ };
408
+
373
409
  // src/runtime/interactions/componentMatcher.ts
374
410
  var ComponentMatcher = class {
375
411
  constructor(options = {}) {
376
412
  __publicField(this, "allowlist");
413
+ // Trusted workspace-folder paths supplied by the host on enable. When set, an
414
+ // element whose `data-source-file` resolves outside every root is not
415
+ // highlightable/selectable — a defense against an attacker-injected
416
+ // `data-source-file` pointing outside the workspace. Empty ⇒ no path gating
417
+ // (the extension's file-read sink remains the authoritative boundary).
418
+ __publicField(this, "workspaceRoots", []);
377
419
  this.allowlist = options.allowlist ?? [];
378
420
  }
421
+ /** Set the trusted workspace roots used to gate highlight/select by source path. */
422
+ setWorkspaceRoots(roots) {
423
+ this.workspaceRoots = roots.slice();
424
+ }
379
425
  /**
380
426
  * Check whether an element carries a *valid, complete* source location.
381
427
  * A bare `data-source-file` attribute is not enough — it must parse into a
@@ -415,11 +461,28 @@
415
461
  if (!this.hasSourceMetadata(element)) {
416
462
  return false;
417
463
  }
464
+ if (!this.isSourceWithinWorkspace(element)) {
465
+ return false;
466
+ }
418
467
  if (this.allowlist.length > 0) {
419
468
  return this.matchesList(element, this.allowlist);
420
469
  }
421
470
  return true;
422
471
  }
472
+ /**
473
+ * Gate on the element's `data-source-file` when workspace roots are known: an
474
+ * **absolute** path outside every root is refused (the attacker-injected case).
475
+ * A relative path is allowed through — it can't escape, and the extension sink
476
+ * resolves it against a real root. No roots known ⇒ allow (defer to the sink).
477
+ */
478
+ isSourceWithinWorkspace(element) {
479
+ if (this.workspaceRoots.length === 0) return true;
480
+ const sourceFile = getSourceFromDataAttributes(element)?.sourceFile;
481
+ if (!sourceFile) return true;
482
+ const isAbsolute = sourceFile.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(sourceFile);
483
+ if (!isAbsolute) return true;
484
+ return isPathContained(sourceFile, this.workspaceRoots);
485
+ }
423
486
  /**
424
487
  * Find the nearest highlightable element by walking up the DOM tree
425
488
  * @param target - The target element
@@ -883,9 +946,12 @@
883
946
  this.communicationManager.notifyInitializationComplete();
884
947
  }
885
948
  /**
886
- * Enable the design mode interactions
949
+ * Enable the design mode interactions. The host passes the trusted workspace
950
+ * roots so elements whose `data-source-file` is outside the workspace are not
951
+ * highlightable/selectable.
887
952
  */
888
- enable() {
953
+ enable(workspaceRoots = []) {
954
+ this.componentMatcher.setWorkspaceRoots(workspaceRoots);
889
955
  this.isActive = true;
890
956
  console.log("Design Mode Interactions enabled");
891
957
  }
@@ -974,8 +1040,8 @@
974
1040
  }
975
1041
  }
976
1042
  if (typeof window !== "undefined") {
977
- window.enableInteractions = function() {
978
- interactions.enable();
1043
+ window.enableInteractions = function(workspaceRoots = []) {
1044
+ interactions.enable(workspaceRoots);
979
1045
  };
980
1046
  window.disableInteractions = function() {
981
1047
  interactions.disable();
@@ -1008,7 +1074,8 @@
1008
1074
  }
1009
1075
  }
1010
1076
  if (typed && typed.type === MSG_ENABLE_INTERACTIONS) {
1011
- window.enableInteractions?.();
1077
+ const roots = Array.isArray(typed.workspaceRoots) ? typed.workspaceRoots.filter((r) => typeof r === "string") : [];
1078
+ window.enableInteractions?.(roots);
1012
1079
  }
1013
1080
  if (typed && typed.type === MSG_DISABLE_INTERACTIONS) {
1014
1081
  window.disableInteractions?.();
@@ -8,7 +8,10 @@ export interface ComponentMatcherOptions {
8
8
  }
9
9
  export declare class ComponentMatcher {
10
10
  private allowlist;
11
+ private workspaceRoots;
11
12
  constructor(options?: ComponentMatcherOptions);
13
+ /** Set the trusted workspace roots used to gate highlight/select by source path. */
14
+ setWorkspaceRoots(roots: readonly string[]): void;
12
15
  /**
13
16
  * Check whether an element carries a *valid, complete* source location.
14
17
  * A bare `data-source-file` attribute is not enough — it must parse into a
@@ -33,6 +36,13 @@ export declare class ComponentMatcher {
33
36
  * @returns True if the element should be highlighted
34
37
  */
35
38
  isHighlightableElement(element: HTMLElement | null | undefined): boolean;
39
+ /**
40
+ * Gate on the element's `data-source-file` when workspace roots are known: an
41
+ * **absolute** path outside every root is refused (the attacker-injected case).
42
+ * A relative path is allowed through — it can't escape, and the extension sink
43
+ * resolves it against a real root. No roots known ⇒ allow (defer to the sink).
44
+ */
45
+ private isSourceWithinWorkspace;
36
46
  /**
37
47
  * Find the nearest highlightable element by walking up the DOM tree
38
48
  * @param target - The target element
@@ -8,7 +8,10 @@ export interface ComponentMatcherOptions {
8
8
  }
9
9
  export declare class ComponentMatcher {
10
10
  private allowlist;
11
+ private workspaceRoots;
11
12
  constructor(options?: ComponentMatcherOptions);
13
+ /** Set the trusted workspace roots used to gate highlight/select by source path. */
14
+ setWorkspaceRoots(roots: readonly string[]): void;
12
15
  /**
13
16
  * Check whether an element carries a *valid, complete* source location.
14
17
  * A bare `data-source-file` attribute is not enough — it must parse into a
@@ -33,6 +36,13 @@ export declare class ComponentMatcher {
33
36
  * @returns True if the element should be highlighted
34
37
  */
35
38
  isHighlightableElement(element: HTMLElement | null | undefined): boolean;
39
+ /**
40
+ * Gate on the element's `data-source-file` when workspace roots are known: an
41
+ * **absolute** path outside every root is refused (the attacker-injected case).
42
+ * A relative path is allowed through — it can't escape, and the extension sink
43
+ * resolves it against a real root. No roots known ⇒ allow (defer to the sink).
44
+ */
45
+ private isSourceWithinWorkspace;
36
46
  /**
37
47
  * Find the nearest highlightable element by walking up the DOM tree
38
48
  * @param target - The target element
@@ -1 +1 @@
1
- {"version":3,"file":"componentMatcher.d.ts","sourceRoot":"","sources":["../../../src/runtime/interactions/componentMatcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,WAAW,uBAAuB;IAEvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,SAAS,CAAW;gBAEhB,OAAO,GAAE,uBAA4B;IAIjD;;;;;;;;OAQG;IACH,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAOnE,OAAO,CAAC,WAAW;IAInB;;;;;OAKG;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAM7D;;;;OAIG;IACH,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAgBxE;;;;OAIG;IACH,wBAAwB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI;CA+BpF"}
1
+ {"version":3,"file":"componentMatcher.d.ts","sourceRoot":"","sources":["../../../src/runtime/interactions/componentMatcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,WAAW,uBAAuB;IAEvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,SAAS,CAAW;IAM5B,OAAO,CAAC,cAAc,CAAgB;gBAE1B,OAAO,GAAE,uBAA4B;IAIjD,oFAAoF;IACpF,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAIjD;;;;;;;;OAQG;IACH,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAOnE,OAAO,CAAC,WAAW;IAInB;;;;;OAKG;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAM7D;;;;OAIG;IACH,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAoBxE;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAS/B;;;;OAIG;IACH,wBAAwB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI;CA+BpF"}
@@ -13,9 +13,11 @@ export declare class InteractionsController {
13
13
  */
14
14
  initialize(): void;
15
15
  /**
16
- * Enable the design mode interactions
16
+ * Enable the design mode interactions. The host passes the trusted workspace
17
+ * roots so elements whose `data-source-file` is outside the workspace are not
18
+ * highlightable/selectable.
17
19
  */
18
- enable(): void;
20
+ enable(workspaceRoots?: readonly string[]): void;
19
21
  /**
20
22
  * Disable the design mode interactions
21
23
  */
@@ -13,9 +13,11 @@ export declare class InteractionsController {
13
13
  */
14
14
  initialize(): void;
15
15
  /**
16
- * Enable the design mode interactions
16
+ * Enable the design mode interactions. The host passes the trusted workspace
17
+ * roots so elements whose `data-source-file` is outside the workspace are not
18
+ * highlightable/selectable.
17
19
  */
18
- enable(): void;
20
+ enable(workspaceRoots?: readonly string[]): void;
19
21
  /**
20
22
  * Disable the design mode interactions
21
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"interactionsController.d.ts","sourceRoot":"","sources":["../../../src/runtime/interactions/interactionsController.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,qBAAqB,CAAC;AAuC7B,qBAAa,sBAAsB;IAClC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAU;IAE1B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,UAAO;IA0D1B;;OAEG;IACH,UAAU,IAAI,IAAI;IAoBlB;;OAEG;IACH,MAAM,IAAI,IAAI;IAKd;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,cAAc;IAWtB;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI;IAOxF,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI;IAOpE;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAC9C,cAAc,CAAC,EAAE,cAAc,GAC7B,IAAI;IAWP;;OAEG;IACH,OAAO,IAAI,IAAI;CASf"}
1
+ {"version":3,"file":"interactionsController.d.ts","sourceRoot":"","sources":["../../../src/runtime/interactions/interactionsController.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,qBAAqB,CAAC;AAuC7B,qBAAa,sBAAsB;IAClC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAU;IAE1B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,UAAO;IA0D1B;;OAEG;IACH,UAAU,IAAI,IAAI;IAoBlB;;;;OAIG;IACH,MAAM,CAAC,cAAc,GAAE,SAAS,MAAM,EAAO,GAAG,IAAI;IAMpD;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,cAAc;IAWtB;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI;IAOxF,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI;IAOpE;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAC9C,cAAc,CAAC,EAAE,cAAc,GAC7B,IAAI;IAWP;;OAEG;IACH,OAAO,IAAI,IAAI;CASf"}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const pathContainment = require("./pathContainment.cjs");
4
+ exports.isPathContained = pathContainment.isPathContained;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ /**
7
+ * `/security` entry: environment-neutral, pure helpers a design-mode surface
8
+ * uses to guard data that originates from the **untrusted preview iframe**.
9
+ * Shared so the logic is written and tested once and consumed by every surface —
10
+ * the iframe runtime (highlight/select gate), the VS Code extension (file-read
11
+ * sink), and Codey Studio. No `vscode`, no `node:fs`/`node:path`, no DOM.
12
+ *
13
+ * - `isPathContained`: string-containment half of the source-file path guard.
14
+ * The extension additionally runs an `fs.stat`/symlink re-check at its sink.
15
+ */
16
+ export { isPathContained } from './pathContainment.cjs';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ /**
7
+ * `/security` entry: environment-neutral, pure helpers a design-mode surface
8
+ * uses to guard data that originates from the **untrusted preview iframe**.
9
+ * Shared so the logic is written and tested once and consumed by every surface —
10
+ * the iframe runtime (highlight/select gate), the VS Code extension (file-read
11
+ * sink), and Codey Studio. No `vscode`, no `node:fs`/`node:path`, no DOM.
12
+ *
13
+ * - `isPathContained`: string-containment half of the source-file path guard.
14
+ * The extension additionally runs an `fs.stat`/symlink re-check at its sink.
15
+ */
16
+ export { isPathContained } from './pathContainment.js';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { isPathContained } from "./pathContainment.js";
2
+ export {
3
+ isPathContained
4
+ };
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const normalizeSeparators = (p) => p.replace(/\\/g, "/");
4
+ const stripTrailingSlash = (p) => {
5
+ const s = p.replace(/\/+$/, "");
6
+ return s === "" && p.startsWith("/") ? "/" : s;
7
+ };
8
+ const normalizeDotSegments = (p) => {
9
+ const normalized = normalizeSeparators(p);
10
+ const isAbsolute = normalized.startsWith("/");
11
+ const out = [];
12
+ for (const seg of normalized.split("/")) {
13
+ if (seg === "" || seg === ".") continue;
14
+ if (seg === "..") {
15
+ if (out.length > 0 && out[out.length - 1] !== "..") out.pop();
16
+ else if (!isAbsolute) out.push("..");
17
+ } else {
18
+ out.push(seg);
19
+ }
20
+ }
21
+ return (isAbsolute ? "/" : "") + out.join("/");
22
+ };
23
+ const isPathContained = (candidate, roots) => {
24
+ if (typeof candidate !== "string" || candidate.trim() === "") return false;
25
+ if (!Array.isArray(roots) || roots.length === 0) return false;
26
+ const normalizedCandidate = stripTrailingSlash(normalizeDotSegments(candidate));
27
+ if (normalizedCandidate === "") return false;
28
+ return roots.some((root) => {
29
+ if (typeof root !== "string" || root.trim() === "") return false;
30
+ const normalizedRoot = stripTrailingSlash(normalizeDotSegments(root));
31
+ if (normalizedRoot === "") return false;
32
+ if (normalizedCandidate === normalizedRoot) return true;
33
+ const prefix = normalizedRoot === "/" ? "/" : `${normalizedRoot}/`;
34
+ return normalizedCandidate.startsWith(prefix);
35
+ });
36
+ };
37
+ exports.isPathContained = isPathContained;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ /**
7
+ * True if the candidate is one of the roots or strictly inside one. Both sides
8
+ * are normalized (separators + `.`/`..` collapsing); matching is exact-segment so
9
+ * `/repo-secrets` is NOT inside `/repo`. Fail-closed: an empty candidate or empty
10
+ * `roots` yields `false`.
11
+ *
12
+ * String containment only — symlinks that escape a root are NOT defeated here;
13
+ * a caller with filesystem access (the extension sink) must re-check before reading.
14
+ */
15
+ export declare const isPathContained: (candidate: string, roots: readonly string[]) => boolean;
16
+ //# sourceMappingURL=pathContainment.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ /**
7
+ * True if the candidate is one of the roots or strictly inside one. Both sides
8
+ * are normalized (separators + `.`/`..` collapsing); matching is exact-segment so
9
+ * `/repo-secrets` is NOT inside `/repo`. Fail-closed: an empty candidate or empty
10
+ * `roots` yields `false`.
11
+ *
12
+ * String containment only — symlinks that escape a root are NOT defeated here;
13
+ * a caller with filesystem access (the extension sink) must re-check before reading.
14
+ */
15
+ export declare const isPathContained: (candidate: string, roots: readonly string[]) => boolean;
16
+ //# sourceMappingURL=pathContainment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathContainment.d.ts","sourceRoot":"","sources":["../../src/security/pathContainment.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6CH;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,KAAG,OAe7E,CAAC"}
@@ -0,0 +1,37 @@
1
+ const normalizeSeparators = (p) => p.replace(/\\/g, "/");
2
+ const stripTrailingSlash = (p) => {
3
+ const s = p.replace(/\/+$/, "");
4
+ return s === "" && p.startsWith("/") ? "/" : s;
5
+ };
6
+ const normalizeDotSegments = (p) => {
7
+ const normalized = normalizeSeparators(p);
8
+ const isAbsolute = normalized.startsWith("/");
9
+ const out = [];
10
+ for (const seg of normalized.split("/")) {
11
+ if (seg === "" || seg === ".") continue;
12
+ if (seg === "..") {
13
+ if (out.length > 0 && out[out.length - 1] !== "..") out.pop();
14
+ else if (!isAbsolute) out.push("..");
15
+ } else {
16
+ out.push(seg);
17
+ }
18
+ }
19
+ return (isAbsolute ? "/" : "") + out.join("/");
20
+ };
21
+ const isPathContained = (candidate, roots) => {
22
+ if (typeof candidate !== "string" || candidate.trim() === "") return false;
23
+ if (!Array.isArray(roots) || roots.length === 0) return false;
24
+ const normalizedCandidate = stripTrailingSlash(normalizeDotSegments(candidate));
25
+ if (normalizedCandidate === "") return false;
26
+ return roots.some((root) => {
27
+ if (typeof root !== "string" || root.trim() === "") return false;
28
+ const normalizedRoot = stripTrailingSlash(normalizeDotSegments(root));
29
+ if (normalizedRoot === "") return false;
30
+ if (normalizedCandidate === normalizedRoot) return true;
31
+ const prefix = normalizedRoot === "/" ? "/" : `${normalizedRoot}/`;
32
+ return normalizedCandidate.startsWith(prefix);
33
+ });
34
+ };
35
+ export {
36
+ isPathContained
37
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@salesforce/ui-design-mode",
3
3
  "description": "Hybrid editor (design-mode) authoring UI, iframe runtime, source-locator, and protocol for Salesforce UI Bundles",
4
- "version": "10.14.1",
4
+ "version": "10.15.1",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "type": "module",
7
7
  "exports": {
@@ -45,6 +45,16 @@
45
45
  "default": "./dist/authoring/host/index.cjs"
46
46
  }
47
47
  },
48
+ "./security": {
49
+ "import": {
50
+ "types": "./dist/security/index.d.ts",
51
+ "default": "./dist/security/index.js"
52
+ },
53
+ "require": {
54
+ "types": "./dist/security/index.d.cts",
55
+ "default": "./dist/security/index.cjs"
56
+ }
57
+ },
48
58
  "./authoring/react": {
49
59
  "types": "./dist/authoring/react/index.d.ts",
50
60
  "import": "./dist/authoring/react/index.js"