@fluid-app/widget-runtime 0.1.0 → 0.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.
Files changed (32) hide show
  1. package/README.md +15 -1
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +1 -1
  4. package/dist/index.d.mts +1 -1
  5. package/dist/index.mjs +1 -1
  6. package/dist/{remote-dom-renderer-BU-73KvK.d.mts → remote-dom-renderer-CKvnUH8G.d.mts} +4 -1
  7. package/dist/remote-dom-renderer-CKvnUH8G.d.mts.map +1 -0
  8. package/dist/{remote-dom-renderer-DSHFetvm.d.cts → remote-dom-renderer-CeYSItkU.d.cts} +4 -1
  9. package/dist/remote-dom-renderer-CeYSItkU.d.cts.map +1 -0
  10. package/dist/{remote-dom-renderer-DcwcNPsu.mjs → remote-dom-renderer-CpdKWANy.mjs} +129 -20
  11. package/dist/remote-dom-renderer-CpdKWANy.mjs.map +1 -0
  12. package/dist/{remote-dom-renderer-DUQf80Nu.cjs → remote-dom-renderer-f7OWlvvv.cjs} +129 -20
  13. package/dist/remote-dom-renderer-f7OWlvvv.cjs.map +1 -0
  14. package/dist/remote-dom.cjs +1 -1
  15. package/dist/remote-dom.d.cts +1 -1
  16. package/dist/remote-dom.d.mts +1 -1
  17. package/dist/remote-dom.mjs +1 -1
  18. package/dist/{worker-react-renderer-BSFxMMf7.cjs → worker-react-renderer-BvHZ2o0B.cjs} +2 -2
  19. package/dist/{worker-react-renderer-BSFxMMf7.cjs.map → worker-react-renderer-BvHZ2o0B.cjs.map} +1 -1
  20. package/dist/worker.cjs +149 -7
  21. package/dist/worker.cjs.map +1 -1
  22. package/dist/worker.d.cts +2 -1
  23. package/dist/worker.d.cts.map +1 -1
  24. package/dist/worker.d.mts +2 -1
  25. package/dist/worker.d.mts.map +1 -1
  26. package/dist/worker.mjs +147 -6
  27. package/dist/worker.mjs.map +1 -1
  28. package/package.json +1 -1
  29. package/dist/remote-dom-renderer-BU-73KvK.d.mts.map +0 -1
  30. package/dist/remote-dom-renderer-DSHFetvm.d.cts.map +0 -1
  31. package/dist/remote-dom-renderer-DUQf80Nu.cjs.map +0 -1
  32. 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 selected network, storage, nested-worker, and fresh-realm globals where the worker environment permits it. Host calls are limited to capabilities declared by the widget package descriptor.
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-DUQf80Nu.cjs");
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-DSHFetvm.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-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-BU-73KvK.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-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-DcwcNPsu.mjs";
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-BU-73KvK.d.mts.map
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-DSHFetvm.d.cts.map
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:") return isAllowedHttpOrigin(parsedUrl.origin, options.originPolicy, options.allowedHttpOriginGroups ?? []) ? trimmedValue : void 0;
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: true,
512
+ allowMailTo: false,
510
513
  allowRelative: true,
511
- allowTel: true,
512
- allowedHttpOriginGroups: [
513
- "artifact",
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 }, { urlOriginPolicy: options.urlOriginPolicy });
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, { urlOriginPolicy: options.urlOriginPolicy });
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 ready = Promise.all([styles.ready, thread.imports.mount({
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
- })]).then(() => void 0);
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-DcwcNPsu.mjs.map
2190
+ //# sourceMappingURL=remote-dom-renderer-CpdKWANy.mjs.map