@fluid-app/widget-runtime 0.1.0 → 0.1.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/README.md +15 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{remote-dom-renderer-BU-73KvK.d.mts → remote-dom-renderer-CKvnUH8G.d.mts} +4 -1
- package/dist/remote-dom-renderer-CKvnUH8G.d.mts.map +1 -0
- package/dist/{remote-dom-renderer-DSHFetvm.d.cts → remote-dom-renderer-CeYSItkU.d.cts} +4 -1
- package/dist/remote-dom-renderer-CeYSItkU.d.cts.map +1 -0
- package/dist/{remote-dom-renderer-DcwcNPsu.mjs → remote-dom-renderer-CpdKWANy.mjs} +129 -20
- package/dist/remote-dom-renderer-CpdKWANy.mjs.map +1 -0
- package/dist/{remote-dom-renderer-DUQf80Nu.cjs → remote-dom-renderer-f7OWlvvv.cjs} +129 -20
- package/dist/remote-dom-renderer-f7OWlvvv.cjs.map +1 -0
- package/dist/remote-dom.cjs +1 -1
- package/dist/remote-dom.d.cts +1 -1
- package/dist/remote-dom.d.mts +1 -1
- package/dist/remote-dom.mjs +1 -1
- package/dist/{worker-react-renderer-BSFxMMf7.cjs → worker-react-renderer-BvHZ2o0B.cjs} +2 -2
- package/dist/{worker-react-renderer-BSFxMMf7.cjs.map → worker-react-renderer-BvHZ2o0B.cjs.map} +1 -1
- package/dist/worker.cjs +149 -7
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +2 -1
- package/dist/worker.d.cts.map +1 -1
- package/dist/worker.d.mts +2 -1
- package/dist/worker.d.mts.map +1 -1
- package/dist/worker.mjs +147 -6
- package/dist/worker.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/remote-dom-renderer-BU-73KvK.d.mts.map +0 -1
- package/dist/remote-dom-renderer-DSHFetvm.d.cts.map +0 -1
- package/dist/remote-dom-renderer-DUQf80Nu.cjs.map +0 -1
- package/dist/remote-dom-renderer-DcwcNPsu.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -55,7 +55,16 @@ function Greeting({ name }: { readonly name: string }) {
|
|
|
55
55
|
startRemoteDomWidgetWorker({ widgets: { Greeting } });
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Workers can render arbitrary supported HTML and CSS through upstream Remote DOM. The runtime disables
|
|
58
|
+
Workers can render arbitrary supported HTML and CSS through upstream Remote DOM. The runtime disables storage, nested-worker, fresh-realm, and non-fetch network globals where the worker environment permits it. Host calls are limited to capabilities declared by the widget package descriptor.
|
|
59
|
+
|
|
60
|
+
`fetch` is denied unless the selected widget declares `networkAccess` version
|
|
61
|
+
`1` and the host supplies an exact persisted grant. Granted calls go directly
|
|
62
|
+
through the preserved native worker fetch; the runtime does not inject Fluid
|
|
63
|
+
credentials or headers. The initial URL cannot address `fluid.app`, the host
|
|
64
|
+
portal origin, loopback, or a private-network IP. DNS resolution and redirect
|
|
65
|
+
destinations are not inspected. A redirect can therefore reach a target that
|
|
66
|
+
would have been rejected as the initial URL. WebSocket, EventSource,
|
|
67
|
+
WebTransport, and streaming-specific APIs remain unavailable in v1.
|
|
59
68
|
|
|
60
69
|
## Threat model
|
|
61
70
|
|
|
@@ -68,6 +77,11 @@ do not constitute a complete network sandbox or a substitute for package
|
|
|
68
77
|
review, catalog authorization, browser isolation, or server-side access
|
|
69
78
|
control.
|
|
70
79
|
|
|
80
|
+
Network-enabled code can send any portal data available to that widget to an
|
|
81
|
+
external service. Worker isolation does not protect the reputation of Fluid
|
|
82
|
+
domains from package behavior; catalog, builder, and CLI approval must treat
|
|
83
|
+
network access as a publisher trust decision.
|
|
84
|
+
|
|
71
85
|
## Non-goals
|
|
72
86
|
|
|
73
87
|
- No classic script loading or browser globals.
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_remote_dom_renderer = require("./remote-dom-renderer-
|
|
2
|
+
const require_remote_dom_renderer = require("./remote-dom-renderer-f7OWlvvv.cjs");
|
|
3
3
|
const require_remote_dom_values = require("./remote-dom-values-DbgIedSg.cjs");
|
|
4
4
|
exports.isRemoteDomSerializableValue = require_remote_dom_values.isRemoteDomSerializableValue;
|
|
5
5
|
exports.mountRemoteDomWidget = require_remote_dom_renderer.mountRemoteDomWidget;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { a as isRemoteDomSerializableValue, i as RemoteDomUrlOriginPolicyInput, n as RemoteDomSerializableValue, o as toRemoteDomSerializableValue, r as RemoteDomUrlOriginPolicy, t as RemoteDomNavigationIntent } from "./remote-dom-values-D4AgBNWr.cjs";
|
|
2
|
-
import { a as RemoteDomWidgetHandle, c as mountRemoteDomWidget, d as RemoteDomStyleAttachment, f as RemoteDomThemeBridgeOptions, i as RemoteDomWidgetArtifact, l as RemoteDomCssFetcher, n as RemoteDomCapabilityDeclaration, o as RemoteDomWorkerFactory, r as RemoteDomFullscreenRequest, s as RemoteDomWorkerLike, t as MountRemoteDomWidgetOptions, u as RemoteDomCssUrlInput } from "./remote-dom-renderer-
|
|
2
|
+
import { a as RemoteDomWidgetHandle, c as mountRemoteDomWidget, d as RemoteDomStyleAttachment, f as RemoteDomThemeBridgeOptions, i as RemoteDomWidgetArtifact, l as RemoteDomCssFetcher, n as RemoteDomCapabilityDeclaration, o as RemoteDomWorkerFactory, r as RemoteDomFullscreenRequest, s as RemoteDomWorkerLike, t as MountRemoteDomWidgetOptions, u as RemoteDomCssUrlInput } from "./remote-dom-renderer-CeYSItkU.cjs";
|
|
3
3
|
export { type MountRemoteDomWidgetOptions, type RemoteDomCapabilityDeclaration, type RemoteDomCssFetcher, type RemoteDomCssUrlInput, type RemoteDomFullscreenRequest, type RemoteDomNavigationIntent, type RemoteDomSerializableValue, type RemoteDomStyleAttachment, type RemoteDomThemeBridgeOptions, type RemoteDomUrlOriginPolicy, type RemoteDomUrlOriginPolicyInput, type RemoteDomWidgetArtifact, type RemoteDomWidgetHandle, type RemoteDomWorkerFactory, type RemoteDomWorkerLike, isRemoteDomSerializableValue, mountRemoteDomWidget, toRemoteDomSerializableValue };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { a as isRemoteDomSerializableValue, i as RemoteDomUrlOriginPolicyInput, n as RemoteDomSerializableValue, o as toRemoteDomSerializableValue, r as RemoteDomUrlOriginPolicy, t as RemoteDomNavigationIntent } from "./remote-dom-values-EwoLXnSn.mjs";
|
|
2
|
-
import { a as RemoteDomWidgetHandle, c as mountRemoteDomWidget, d as RemoteDomStyleAttachment, f as RemoteDomThemeBridgeOptions, i as RemoteDomWidgetArtifact, l as RemoteDomCssFetcher, n as RemoteDomCapabilityDeclaration, o as RemoteDomWorkerFactory, r as RemoteDomFullscreenRequest, s as RemoteDomWorkerLike, t as MountRemoteDomWidgetOptions, u as RemoteDomCssUrlInput } from "./remote-dom-renderer-
|
|
2
|
+
import { a as RemoteDomWidgetHandle, c as mountRemoteDomWidget, d as RemoteDomStyleAttachment, f as RemoteDomThemeBridgeOptions, i as RemoteDomWidgetArtifact, l as RemoteDomCssFetcher, n as RemoteDomCapabilityDeclaration, o as RemoteDomWorkerFactory, r as RemoteDomFullscreenRequest, s as RemoteDomWorkerLike, t as MountRemoteDomWidgetOptions, u as RemoteDomCssUrlInput } from "./remote-dom-renderer-CKvnUH8G.mjs";
|
|
3
3
|
export { type MountRemoteDomWidgetOptions, type RemoteDomCapabilityDeclaration, type RemoteDomCssFetcher, type RemoteDomCssUrlInput, type RemoteDomFullscreenRequest, type RemoteDomNavigationIntent, type RemoteDomSerializableValue, type RemoteDomStyleAttachment, type RemoteDomThemeBridgeOptions, type RemoteDomUrlOriginPolicy, type RemoteDomUrlOriginPolicyInput, type RemoteDomWidgetArtifact, type RemoteDomWidgetHandle, type RemoteDomWorkerFactory, type RemoteDomWorkerLike, isRemoteDomSerializableValue, mountRemoteDomWidget, toRemoteDomSerializableValue };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { c as isRemoteDomSerializableValue, u as toRemoteDomSerializableValue } from "./remote-dom-values-BSAxrOE7.mjs";
|
|
2
|
-
import { t as mountRemoteDomWidget } from "./remote-dom-renderer-
|
|
2
|
+
import { t as mountRemoteDomWidget } from "./remote-dom-renderer-CpdKWANy.mjs";
|
|
3
3
|
export { isRemoteDomSerializableValue, mountRemoteDomWidget, toRemoteDomSerializableValue };
|
|
@@ -37,6 +37,7 @@ interface RemoteDomCapabilityDeclaration {
|
|
|
37
37
|
interface RemoteDomWidgetArtifact extends RemoteDomStyleArtifact {
|
|
38
38
|
readonly widgetType: string;
|
|
39
39
|
readonly capabilities: readonly RemoteDomCapabilityDeclaration[];
|
|
40
|
+
readonly networkAccessGranted?: boolean | undefined;
|
|
40
41
|
}
|
|
41
42
|
interface RemoteDomFullscreenRequest {
|
|
42
43
|
readonly method: string;
|
|
@@ -55,6 +56,8 @@ interface MountRemoteDomWidgetOptions {
|
|
|
55
56
|
readonly workerFactory?: RemoteDomWorkerFactory | undefined;
|
|
56
57
|
readonly hostOrigin?: string | undefined;
|
|
57
58
|
readonly mediaOrigins?: readonly string[] | undefined;
|
|
59
|
+
/** Canonical absolute HTTP(S) URLs registered for native new-tab anchors. */
|
|
60
|
+
readonly allowedAnchorUrls?: ReadonlySet<string> | undefined;
|
|
58
61
|
readonly maxMutationBatchSize?: number | undefined;
|
|
59
62
|
readonly maxDomNodeCount?: number | undefined;
|
|
60
63
|
readonly cssText?: string | undefined;
|
|
@@ -87,4 +90,4 @@ type RemoteDomWorkerFactory = (workerEntryUrl: string, options: WorkerOptions) =
|
|
|
87
90
|
declare function mountRemoteDomWidget(options: MountRemoteDomWidgetOptions): RemoteDomWidgetHandle;
|
|
88
91
|
//#endregion
|
|
89
92
|
export { RemoteDomWidgetHandle as a, mountRemoteDomWidget as c, RemoteDomStyleAttachment as d, RemoteDomThemeBridgeOptions as f, RemoteDomWidgetArtifact as i, RemoteDomCssFetcher as l, RemoteDomCapabilityDeclaration as n, RemoteDomWorkerFactory as o, RemoteDomFullscreenRequest as r, RemoteDomWorkerLike as s, MountRemoteDomWidgetOptions as t, RemoteDomCssUrlInput as u };
|
|
90
|
-
//# sourceMappingURL=remote-dom-renderer-
|
|
93
|
+
//# sourceMappingURL=remote-dom-renderer-CKvnUH8G.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-dom-renderer-CKvnUH8G.d.mts","names":[],"sources":["../src/remote-dom-styles.ts","../src/remote-dom-renderer.ts"],"mappings":";;;UASiB,wBAAA;EAAA,SACN,KAAA,EAAO,OAAA;EAAA,SACP,OAAA;AAAA;AAAA,UAGM,sBAAA;EAAA,SACN,SAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA;EAAA,SACA,OAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,qBAAA;EAAA,SACN,GAAA;EAAA,SACA,OAAA;AAAA;AAAA,KAGC,oBAAA,YAAgC,qBAAA;AAAA,KAEhC,mBAAA,IAAuB,GAAA,aAAgB,OAAA;AAAA,UAOlC,2BAAA;EAAA,SACN,IAAA,EAAM,UAAA;EAAA,SACN,aAAA,GAAgB,OAAA;EAAA,SAChB,aAAA,GAAgB,IAAA;EAAA,SAChB,OAAA;EAAA,SACA,gBAAA;EAAA,SACA,cAAA;EAAA,SACA,UAAA;EAAA,SACA,qBAAA;AAAA;;;UCDM,8BAAA;EAAA,SACN,IAAA;EAAA,SACA,OAAA;AAAA;AAAA,UAGM,uBAAA,SAAgC,sBAAA;EAAA,SACtC,UAAA;EAAA,SACA,YAAA,WAAuB,8BAAA;EAAA,SACvB,oBAAA;AAAA;AAAA,UAGM,0BAAA;EAAA,SACN,MAAA;EAAA,SACA,OAAA,GAAU,0BAAA;EAAA,SACV,IAAA,EAAM,WAAA;EAAA,SACN,UAAA,EAAY,UAAA;AAAA;AAAA,UAGN,2BAAA;EAAA,SACN,IAAA,EAAM,WAAA;EAAA,SACN,QAAA,EAAU,uBAAA;EAAA,SACV,KAAA,GAAQ,0BAAA;EAAA,SACR,cAAA,KAEH,UAAA,UACA,MAAA,UACA,OAAA,GAAU,0BAAA,KACP,0BAAA,GAA6B,OAAA,CAAQ,0BAAA;EAAA,SAErC,UAAA,KACH,MAAA,EAAQ,yBAAA,YAAqC,OAAA;EAAA,SAE1C,YAAA,KAEH,OAAA,EAAS,0BAAA,KACN,0BAAA,GAA6B,OAAA,CAAQ,0BAAA;EAAA,SAErC,OAAA,KAAY,KAAA,EAAO,KAAA;EAAA,SACnB,aAAA,GAAgB,sBAAA;EAAA,SAChB,UAAA;EAAA,SACA,YAAA;EDxDqB;EAAA,SC0DrB,iBAAA,GAAoB,WAAA;EAAA,SACpB,oBAAA;EAAA,SACA,eAAA;EAAA,SACA,OAAA;EAAA,SACA,aAAA;EAAA,SACA,OAAA,YAAmB,oBAAA;EAAA,SACnB,qBAAA;EAAA,SACA,qBAAA,YAAiC,oBAAA;EAAA,SACjC,kBAAA;EAAA,SACA,WAAA,GACL,IAAA,CAAK,2BAAA;EAAA,SAGA,QAAA,GAAW,mBAAA;EAAA,SACX,2BAAA;AAAA;AAAA,UAGM,qBAAA;EAAA,SACN,UAAA,EAAY,UAAA;EAAA,SACZ,MAAA,EAAQ,wBAAA;EAAA,SACR,KAAA,EAAO,OAAA;EAChB,WAAA,CAAY,KAAA,EAAO,0BAAA;EACnB,OAAA;AAAA;AAAA,UAGe,mBAAA;EACf,SAAA,IAAa,KAAA,EAAO,YAAA;EACpB,OAAA,IAAW,KAAA,EAAO,UAAA;EAClB,WAAA,CAAY,OAAA,WAAkB,QAAA,GAAW,YAAA;EACzC,SAAA;EACA,gBAAA,IACE,IAAA,aACA,QAAA,GAAW,KAAA,EAAO,YAAA,oBAClB,OAAA;IAAY,MAAA,GAAS,WAAA;EAAA;AAAA;AAAA,KAIb,sBAAA,IACV,cAAA,UACA,OAAA,EAAS,aAAA,KACN,mBAAA;AAAA,iBAqCW,oBAAA,CACd,OAAA,EAAS,2BAAA,GACR,qBAAA"}
|
|
@@ -37,6 +37,7 @@ interface RemoteDomCapabilityDeclaration {
|
|
|
37
37
|
interface RemoteDomWidgetArtifact extends RemoteDomStyleArtifact {
|
|
38
38
|
readonly widgetType: string;
|
|
39
39
|
readonly capabilities: readonly RemoteDomCapabilityDeclaration[];
|
|
40
|
+
readonly networkAccessGranted?: boolean | undefined;
|
|
40
41
|
}
|
|
41
42
|
interface RemoteDomFullscreenRequest {
|
|
42
43
|
readonly method: string;
|
|
@@ -55,6 +56,8 @@ interface MountRemoteDomWidgetOptions {
|
|
|
55
56
|
readonly workerFactory?: RemoteDomWorkerFactory | undefined;
|
|
56
57
|
readonly hostOrigin?: string | undefined;
|
|
57
58
|
readonly mediaOrigins?: readonly string[] | undefined;
|
|
59
|
+
/** Canonical absolute HTTP(S) URLs registered for native new-tab anchors. */
|
|
60
|
+
readonly allowedAnchorUrls?: ReadonlySet<string> | undefined;
|
|
58
61
|
readonly maxMutationBatchSize?: number | undefined;
|
|
59
62
|
readonly maxDomNodeCount?: number | undefined;
|
|
60
63
|
readonly cssText?: string | undefined;
|
|
@@ -87,4 +90,4 @@ type RemoteDomWorkerFactory = (workerEntryUrl: string, options: WorkerOptions) =
|
|
|
87
90
|
declare function mountRemoteDomWidget(options: MountRemoteDomWidgetOptions): RemoteDomWidgetHandle;
|
|
88
91
|
//#endregion
|
|
89
92
|
export { RemoteDomWidgetHandle as a, mountRemoteDomWidget as c, RemoteDomStyleAttachment as d, RemoteDomThemeBridgeOptions as f, RemoteDomWidgetArtifact as i, RemoteDomCssFetcher as l, RemoteDomCapabilityDeclaration as n, RemoteDomWorkerFactory as o, RemoteDomFullscreenRequest as r, RemoteDomWorkerLike as s, MountRemoteDomWidgetOptions as t, RemoteDomCssUrlInput as u };
|
|
90
|
-
//# sourceMappingURL=remote-dom-renderer-
|
|
93
|
+
//# sourceMappingURL=remote-dom-renderer-CeYSItkU.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-dom-renderer-CeYSItkU.d.cts","names":[],"sources":["../src/remote-dom-styles.ts","../src/remote-dom-renderer.ts"],"mappings":";;;UASiB,wBAAA;EAAA,SACN,KAAA,EAAO,OAAA;EAAA,SACP,OAAA;AAAA;AAAA,UAGM,sBAAA;EAAA,SACN,SAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA;EAAA,SACA,OAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,qBAAA;EAAA,SACN,GAAA;EAAA,SACA,OAAA;AAAA;AAAA,KAGC,oBAAA,YAAgC,qBAAA;AAAA,KAEhC,mBAAA,IAAuB,GAAA,aAAgB,OAAA;AAAA,UAOlC,2BAAA;EAAA,SACN,IAAA,EAAM,UAAA;EAAA,SACN,aAAA,GAAgB,OAAA;EAAA,SAChB,aAAA,GAAgB,IAAA;EAAA,SAChB,OAAA;EAAA,SACA,gBAAA;EAAA,SACA,cAAA;EAAA,SACA,UAAA;EAAA,SACA,qBAAA;AAAA;;;UCDM,8BAAA;EAAA,SACN,IAAA;EAAA,SACA,OAAA;AAAA;AAAA,UAGM,uBAAA,SAAgC,sBAAA;EAAA,SACtC,UAAA;EAAA,SACA,YAAA,WAAuB,8BAAA;EAAA,SACvB,oBAAA;AAAA;AAAA,UAGM,0BAAA;EAAA,SACN,MAAA;EAAA,SACA,OAAA,GAAU,0BAAA;EAAA,SACV,IAAA,EAAM,WAAA;EAAA,SACN,UAAA,EAAY,UAAA;AAAA;AAAA,UAGN,2BAAA;EAAA,SACN,IAAA,EAAM,WAAA;EAAA,SACN,QAAA,EAAU,uBAAA;EAAA,SACV,KAAA,GAAQ,0BAAA;EAAA,SACR,cAAA,KAEH,UAAA,UACA,MAAA,UACA,OAAA,GAAU,0BAAA,KACP,0BAAA,GAA6B,OAAA,CAAQ,0BAAA;EAAA,SAErC,UAAA,KACH,MAAA,EAAQ,yBAAA,YAAqC,OAAA;EAAA,SAE1C,YAAA,KAEH,OAAA,EAAS,0BAAA,KACN,0BAAA,GAA6B,OAAA,CAAQ,0BAAA;EAAA,SAErC,OAAA,KAAY,KAAA,EAAO,KAAA;EAAA,SACnB,aAAA,GAAgB,sBAAA;EAAA,SAChB,UAAA;EAAA,SACA,YAAA;EDxDqB;EAAA,SC0DrB,iBAAA,GAAoB,WAAA;EAAA,SACpB,oBAAA;EAAA,SACA,eAAA;EAAA,SACA,OAAA;EAAA,SACA,aAAA;EAAA,SACA,OAAA,YAAmB,oBAAA;EAAA,SACnB,qBAAA;EAAA,SACA,qBAAA,YAAiC,oBAAA;EAAA,SACjC,kBAAA;EAAA,SACA,WAAA,GACL,IAAA,CAAK,2BAAA;EAAA,SAGA,QAAA,GAAW,mBAAA;EAAA,SACX,2BAAA;AAAA;AAAA,UAGM,qBAAA;EAAA,SACN,UAAA,EAAY,UAAA;EAAA,SACZ,MAAA,EAAQ,wBAAA;EAAA,SACR,KAAA,EAAO,OAAA;EAChB,WAAA,CAAY,KAAA,EAAO,0BAAA;EACnB,OAAA;AAAA;AAAA,UAGe,mBAAA;EACf,SAAA,IAAa,KAAA,EAAO,YAAA;EACpB,OAAA,IAAW,KAAA,EAAO,UAAA;EAClB,WAAA,CAAY,OAAA,WAAkB,QAAA,GAAW,YAAA;EACzC,SAAA;EACA,gBAAA,IACE,IAAA,aACA,QAAA,GAAW,KAAA,EAAO,YAAA,oBAClB,OAAA;IAAY,MAAA,GAAS,WAAA;EAAA;AAAA;AAAA,KAIb,sBAAA,IACV,cAAA,UACA,OAAA,EAAS,aAAA,KACN,mBAAA;AAAA,iBAqCW,oBAAA,CACd,OAAA,EAAS,2BAAA,GACR,qBAAA"}
|
|
@@ -398,7 +398,7 @@ function sanitizeRemoteDomAttribute(tagName, rawName, rawValue, options = {}) {
|
|
|
398
398
|
} : void 0;
|
|
399
399
|
}
|
|
400
400
|
if (URL_VALUED_ATTRIBUTES.has(name)) {
|
|
401
|
-
const value = sanitizeUrlValuedAttribute(normalizedTagName, name, rawValue, options.urlOriginPolicy);
|
|
401
|
+
const value = sanitizeUrlValuedAttribute(normalizedTagName, name, rawValue, options.allowedAnchorUrls, options.urlOriginPolicy);
|
|
402
402
|
return value ? {
|
|
403
403
|
name,
|
|
404
404
|
value
|
|
@@ -452,7 +452,10 @@ function sanitizeRemoteDomUrl(value, options) {
|
|
|
452
452
|
} catch {
|
|
453
453
|
return;
|
|
454
454
|
}
|
|
455
|
-
if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:")
|
|
455
|
+
if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
|
|
456
|
+
if (options.allowedHttpUrls?.has(parsedUrl.href)) return parsedUrl.href;
|
|
457
|
+
return isAllowedHttpOrigin(parsedUrl.origin, options.originPolicy, options.allowedHttpOriginGroups ?? []) ? trimmedValue : void 0;
|
|
458
|
+
}
|
|
456
459
|
if (options.allowMailTo && parsedUrl.protocol === "mailto:") return trimmedValue;
|
|
457
460
|
if (options.allowTel && parsedUrl.protocol === "tel:") return trimmedValue;
|
|
458
461
|
}
|
|
@@ -503,17 +506,14 @@ function readCustomEventDetail(event) {
|
|
|
503
506
|
return {};
|
|
504
507
|
}
|
|
505
508
|
}
|
|
506
|
-
function sanitizeUrlValuedAttribute(tagName, name, value, originPolicy) {
|
|
507
|
-
if (name === "href") return sanitizeRemoteDomUrl(value, {
|
|
509
|
+
function sanitizeUrlValuedAttribute(tagName, name, value, allowedAnchorUrls, originPolicy) {
|
|
510
|
+
if (name === "href" && tagName === "a") return sanitizeRemoteDomUrl(value, {
|
|
508
511
|
allowHashOnly: true,
|
|
509
|
-
allowMailTo:
|
|
512
|
+
allowMailTo: false,
|
|
510
513
|
allowRelative: true,
|
|
511
|
-
allowTel:
|
|
512
|
-
allowedHttpOriginGroups: [
|
|
513
|
-
|
|
514
|
-
"media",
|
|
515
|
-
"navigation"
|
|
516
|
-
],
|
|
514
|
+
allowTel: false,
|
|
515
|
+
allowedHttpOriginGroups: ["navigation"],
|
|
516
|
+
...allowedAnchorUrls ? { allowedHttpUrls: allowedAnchorUrls } : {},
|
|
517
517
|
originPolicy,
|
|
518
518
|
relativeUrlBase: "navigation"
|
|
519
519
|
});
|
|
@@ -630,9 +630,75 @@ function sanitizeCssComponentValueUrlReferences(cssValue, originPolicy) {
|
|
|
630
630
|
const parsed = valueParser(cssValue);
|
|
631
631
|
return sanitizeParsedCssValue(parsed.nodes, originPolicy) ? valueParser.stringify(parsed.nodes) : void 0;
|
|
632
632
|
}
|
|
633
|
+
/** True when a word token ends in a hex escape that CSS would terminate
|
|
634
|
+
* with the following whitespace.
|
|
635
|
+
*
|
|
636
|
+
* CSS Syntax §4.3.7: a hex escape consumes one trailing whitespace
|
|
637
|
+
* character, so `\75 rl(…)` is a SINGLE function token named `url`.
|
|
638
|
+
* postcss-value-parser instead splits on that whitespace and hands us
|
|
639
|
+
* [word "\75", space, function "rl"] — a function whose name decodes
|
|
640
|
+
* to `rl`, which is in neither the url nor the rejected-function set,
|
|
641
|
+
* so the value sails past the origin policy and Chromium then fetches
|
|
642
|
+
* the attacker's URL. Same for `u\72 l(`, `ur\6c (`, `\69 mage-set(`
|
|
643
|
+
* and `image-se\74 (`, and for tab/newline/form-feed as the
|
|
644
|
+
* terminator.
|
|
645
|
+
*
|
|
646
|
+
* We cannot re-join the tokens without reimplementing the tokenizer,
|
|
647
|
+
* so we fail closed on the shape instead. Escapes inside strings are
|
|
648
|
+
* unaffected — those are `string` nodes, so `content: "\201C "` and
|
|
649
|
+
* friends still round-trip. */
|
|
650
|
+
function hasWhitespaceTerminatedEscape(node) {
|
|
651
|
+
return node.type === "word" && /\\[0-9a-fA-F]{1,6}$/.test(node.value);
|
|
652
|
+
}
|
|
653
|
+
/** True when `nodes[index]` is the first half of a function name that
|
|
654
|
+
* CSS will re-join across the whitespace the escape consumes.
|
|
655
|
+
*
|
|
656
|
+
* Narrower than "any word ending in an escape", deliberately. Only a
|
|
657
|
+
* FUNCTION can fetch anything, so the shape that matters is
|
|
658
|
+
* word-escape → single whitespace → function: `\75 rl(…)` re-joins to
|
|
659
|
+
* `url(…)`. Re-joining a word onto another WORD just yields an
|
|
660
|
+
* identifier, which is inert — and rejecting those took ordinary
|
|
661
|
+
* declarations like `color: \72 ed` down with them. Since one rejected
|
|
662
|
+
* declaration invalidates the entire source, that discarded whole
|
|
663
|
+
* stylesheets over a value that could never fetch.
|
|
664
|
+
*
|
|
665
|
+
* The whitespace run must be exactly ONE CSS whitespace: §4.3.7 has
|
|
666
|
+
* the escape consume one, so `\75 rl(` (two spaces) leaves a real
|
|
667
|
+
* separator behind and is two tokens, not a disguised function.
|
|
668
|
+
*
|
|
669
|
+
* Counted after normalizing newlines, not in raw code units. CSS
|
|
670
|
+
* preprocessing (§3.3) folds CRLF and a lone CR into a single LF
|
|
671
|
+
* BEFORE tokenizing, so `\75\r\nrl(` is one whitespace to the browser
|
|
672
|
+
* and resolves to `url(...)`. Measuring the raw string made that two
|
|
673
|
+
* characters, which skipped the rejection entirely and handed the
|
|
674
|
+
* attacker back the bypass. */
|
|
675
|
+
function startsDisguisedFunction(nodes, index) {
|
|
676
|
+
let name = "";
|
|
677
|
+
let i = index;
|
|
678
|
+
for (;;) {
|
|
679
|
+
const word = nodes[i];
|
|
680
|
+
if (!word || !hasWhitespaceTerminatedEscape(word)) return false;
|
|
681
|
+
const separator = nodes[i + 1];
|
|
682
|
+
if (separator?.type !== "space") return false;
|
|
683
|
+
if (separator.value.replace(/\r\n?|\f/g, "\n").length !== 1) return false;
|
|
684
|
+
const decodedWord = decodeCssEscapes(word.value);
|
|
685
|
+
if (decodedWord === void 0) return true;
|
|
686
|
+
name += decodedWord;
|
|
687
|
+
const next = nodes[i + 2];
|
|
688
|
+
if (next?.type === "function") {
|
|
689
|
+
const decodedSuffix = decodeCssEscapes(next.value);
|
|
690
|
+
if (decodedSuffix === void 0) return true;
|
|
691
|
+
const reconstructed = `${name}${decodedSuffix}`.toLowerCase();
|
|
692
|
+
return reconstructed === CSS_URL_FUNCTION_NAME || CSS_REJECTED_URL_FUNCTION_NAMES.has(reconstructed);
|
|
693
|
+
}
|
|
694
|
+
if (next?.type !== "word") return false;
|
|
695
|
+
i += 2;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
633
698
|
function sanitizeParsedCssValue(nodes, originPolicy) {
|
|
634
|
-
for (const node of nodes) {
|
|
699
|
+
for (const [index, node] of nodes.entries()) {
|
|
635
700
|
if ("unclosed" in node && node.unclosed === true) return false;
|
|
701
|
+
if (startsDisguisedFunction(nodes, index)) return false;
|
|
636
702
|
if (node.type !== "function") continue;
|
|
637
703
|
const decodedFunctionName = decodeCssEscapes(node.value);
|
|
638
704
|
if (decodedFunctionName === void 0) return false;
|
|
@@ -952,7 +1018,10 @@ function normalizeUpdatePropertyRecordForHost(options, state, mutation, pendingE
|
|
|
952
1018
|
message: `Remote DOM property "${property}" had an invalid value for "${node.localName}".`
|
|
953
1019
|
};
|
|
954
1020
|
}
|
|
955
|
-
const [attribute] = sanitizeRemoteDomAttributes(node.localName, { [property]: value }, {
|
|
1021
|
+
const [attribute] = sanitizeRemoteDomAttributes(node.localName, { [property]: value }, {
|
|
1022
|
+
...options.allowedAnchorUrls ? { allowedAnchorUrls: options.allowedAnchorUrls } : {},
|
|
1023
|
+
urlOriginPolicy: options.urlOriginPolicy
|
|
1024
|
+
});
|
|
956
1025
|
if (attribute) return {
|
|
957
1026
|
ok: true,
|
|
958
1027
|
record: [
|
|
@@ -1044,7 +1113,10 @@ function normalizeRemoteNodeForHost(options, state, value, seen, pendingEventLis
|
|
|
1044
1113
|
};
|
|
1045
1114
|
}
|
|
1046
1115
|
}
|
|
1047
|
-
const attributes = sanitizeRemoteDomAttributes(tagName, attributeProperties, {
|
|
1116
|
+
const attributes = sanitizeRemoteDomAttributes(tagName, attributeProperties, {
|
|
1117
|
+
...options.allowedAnchorUrls ? { allowedAnchorUrls: options.allowedAnchorUrls } : {},
|
|
1118
|
+
urlOriginPolicy: options.urlOriginPolicy
|
|
1119
|
+
});
|
|
1048
1120
|
const remoteAttributes = {};
|
|
1049
1121
|
for (const attribute of attributes) remoteAttributes[attribute.name] = attribute.value;
|
|
1050
1122
|
if (tagName === "button" && remoteAttributes.type === void 0) remoteAttributes.type = "button";
|
|
@@ -1664,6 +1736,7 @@ function mountRemoteDomWidget(options) {
|
|
|
1664
1736
|
throw new RemoteDomConnectionCallDeniedError("remote element method");
|
|
1665
1737
|
}
|
|
1666
1738
|
}).connection,
|
|
1739
|
+
...options.allowedAnchorUrls ? { allowedAnchorUrls: options.allowedAnchorUrls } : {},
|
|
1667
1740
|
urlOriginPolicy,
|
|
1668
1741
|
maxMutationBatchSize: options.maxMutationBatchSize ?? DEFAULT_MAX_MUTATION_BATCH_SIZE,
|
|
1669
1742
|
maxDomNodeCount: options.maxDomNodeCount ?? DEFAULT_MAX_DOM_NODE_COUNT,
|
|
@@ -1703,18 +1776,17 @@ function mountRemoteDomWidget(options) {
|
|
|
1703
1776
|
shadowRoot.addEventListener("submit", submitListener, { capture: true });
|
|
1704
1777
|
shadowRoot.addEventListener("click", navigationListener, { capture: true });
|
|
1705
1778
|
worker.onerror = (event) => {
|
|
1779
|
+
event.preventDefault?.();
|
|
1706
1780
|
if (state.disposed) return;
|
|
1707
1781
|
reportError(options, event.message || "Remote DOM widget worker failed", event.error);
|
|
1708
1782
|
disposeAfterFailure(state, false);
|
|
1709
1783
|
};
|
|
1710
|
-
const
|
|
1711
|
-
widgetType: options.artifact.widgetType,
|
|
1712
|
-
props: initialProps
|
|
1713
|
-
}, {
|
|
1784
|
+
const hostApi = {
|
|
1714
1785
|
mutate(records) {
|
|
1715
1786
|
if (state.disposed) return;
|
|
1716
1787
|
connection.mutate(records);
|
|
1717
1788
|
rebuildNodeIndex(state);
|
|
1789
|
+
enforceAllowedAnchorSafety(state);
|
|
1718
1790
|
flushPendingListenerUpdates(state);
|
|
1719
1791
|
},
|
|
1720
1792
|
callCapability(capability, method, payload) {
|
|
@@ -1723,7 +1795,15 @@ function mountRemoteDomWidget(options) {
|
|
|
1723
1795
|
reportError(error) {
|
|
1724
1796
|
reportError(options, error.message);
|
|
1725
1797
|
}
|
|
1726
|
-
}
|
|
1798
|
+
};
|
|
1799
|
+
const ready = Promise.all([styles.ready, thread.imports.mount({
|
|
1800
|
+
widgetType: options.artifact.widgetType,
|
|
1801
|
+
props: initialProps,
|
|
1802
|
+
...options.artifact.networkAccessGranted === true ? {
|
|
1803
|
+
networkAccessGranted: true,
|
|
1804
|
+
hostOrigin: options.hostOrigin
|
|
1805
|
+
} : {}
|
|
1806
|
+
}, hostApi)]).then(() => void 0);
|
|
1727
1807
|
ready.catch((error) => {
|
|
1728
1808
|
if (state.disposed) return;
|
|
1729
1809
|
reportError(options, "Remote DOM widget failed to mount", error);
|
|
@@ -1849,6 +1929,10 @@ function handleNavigationIntent(state, event) {
|
|
|
1849
1929
|
const anchor = (event.target instanceof Element ? event.target : event.target instanceof Text ? event.target.parentElement : null)?.closest("a[href]");
|
|
1850
1930
|
if (!(anchor instanceof HTMLAnchorElement)) return;
|
|
1851
1931
|
if (!state.shadowRoot.contains(anchor)) return;
|
|
1932
|
+
if (resolveAllowedAnchorUrl(anchor.getAttribute("href"), state.options.allowedAnchorUrls)) {
|
|
1933
|
+
enforceAllowedAnchorSafetyAttributes(anchor);
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1852
1936
|
if (event.cancelable) event.preventDefault();
|
|
1853
1937
|
const href = sanitizeRemoteDomUrl(anchor.getAttribute("href"), {
|
|
1854
1938
|
allowHashOnly: true,
|
|
@@ -1944,6 +2028,31 @@ function normalizeNavigationOrigin(value) {
|
|
|
1944
2028
|
return;
|
|
1945
2029
|
}
|
|
1946
2030
|
}
|
|
2031
|
+
function resolveAllowedAnchorUrl(href, allowedAnchorUrls) {
|
|
2032
|
+
if (!href || !allowedAnchorUrls) return void 0;
|
|
2033
|
+
let parsed;
|
|
2034
|
+
try {
|
|
2035
|
+
parsed = new URL(href);
|
|
2036
|
+
} catch {
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return;
|
|
2040
|
+
return allowedAnchorUrls.has(parsed.href) ? parsed.href : void 0;
|
|
2041
|
+
}
|
|
2042
|
+
function enforceAllowedAnchorSafety(state) {
|
|
2043
|
+
for (const anchor of state.shadowRoot.querySelectorAll("a[href]")) {
|
|
2044
|
+
if (!resolveAllowedAnchorUrl(anchor.getAttribute("href"), state.options.allowedAnchorUrls)) continue;
|
|
2045
|
+
enforceAllowedAnchorSafetyAttributes(anchor);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
function enforceAllowedAnchorSafetyAttributes(anchor) {
|
|
2049
|
+
anchor.target = "_blank";
|
|
2050
|
+
const relTokens = new Set(anchor.rel.split(/\s+/u).filter(Boolean));
|
|
2051
|
+
relTokens.add("noopener");
|
|
2052
|
+
relTokens.add("noreferrer");
|
|
2053
|
+
anchor.rel = Array.from(relTokens).join(" ");
|
|
2054
|
+
anchor.removeAttribute("download");
|
|
2055
|
+
}
|
|
1947
2056
|
function resolveInternalNavigationUrl(href, navigationOrigin) {
|
|
1948
2057
|
if (href.startsWith("#")) return href;
|
|
1949
2058
|
if (href.startsWith("//") || /^https?:(?!\/\/)/iu.test(href)) return;
|
|
@@ -2078,4 +2187,4 @@ function isPromiseLike(value) {
|
|
|
2078
2187
|
//#endregion
|
|
2079
2188
|
export { mountRemoteDomWidget as t };
|
|
2080
2189
|
|
|
2081
|
-
//# sourceMappingURL=remote-dom-renderer-
|
|
2190
|
+
//# sourceMappingURL=remote-dom-renderer-CpdKWANy.mjs.map
|