@salesforce/ui-design-mode 10.14.1 → 10.15.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/protocol/messageTypes.d.cts +7 -0
- package/dist/protocol/messageTypes.d.ts +7 -0
- package/dist/protocol/messageTypes.d.ts.map +1 -1
- package/dist/runtime/design-mode-interactions.js +72 -5
- package/dist/runtime/interactions/componentMatcher.d.cts +10 -0
- package/dist/runtime/interactions/componentMatcher.d.ts +10 -0
- package/dist/runtime/interactions/componentMatcher.d.ts.map +1 -1
- package/dist/runtime/interactions/interactionsController.d.cts +4 -2
- package/dist/runtime/interactions/interactionsController.d.ts +4 -2
- package/dist/runtime/interactions/interactionsController.d.ts.map +1 -1
- package/dist/security/index.cjs +4 -0
- package/dist/security/index.d.cts +17 -0
- package/dist/security/index.d.ts +17 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +4 -0
- package/dist/security/pathContainment.cjs +37 -0
- package/dist/security/pathContainment.d.cts +16 -0
- package/dist/security/pathContainment.d.ts +16 -0
- package/dist/security/pathContainment.d.ts.map +1 -0
- package/dist/security/pathContainment.js +37 -0
- 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":"AAAA;;;;GAIG;AAEH;;;;;;;;;GASG;AAEH;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACrB;AAID,MAAM,WAAW,yBAAyB;IACzC,IAAI,EAAE,qBAAqB,CAAC;
|
|
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
|
-
|
|
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;
|
|
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
|
|
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,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,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.
|
|
4
|
+
"version": "10.15.0",
|
|
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"
|