@agent-native/core 0.35.3 → 0.36.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.
Files changed (62) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/cli/skills.d.ts.map +1 -1
  4. package/dist/cli/skills.js +171 -11
  5. package/dist/cli/skills.js.map +1 -1
  6. package/dist/client/AssistantChat.d.ts.map +1 -1
  7. package/dist/client/AssistantChat.js +17 -21
  8. package/dist/client/AssistantChat.js.map +1 -1
  9. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  10. package/dist/client/MultiTabAssistantChat.js +18 -5
  11. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  12. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  13. package/dist/client/agent-chat-adapter.js +68 -24
  14. package/dist/client/agent-chat-adapter.js.map +1 -1
  15. package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
  16. package/dist/client/composer/ComposerPlusMenu.js +174 -8
  17. package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
  18. package/dist/client/composer/PromptComposer.d.ts +2 -0
  19. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  20. package/dist/client/composer/PromptComposer.js +2 -2
  21. package/dist/client/composer/PromptComposer.js.map +1 -1
  22. package/dist/client/composer/TiptapComposer.js +1 -1
  23. package/dist/client/composer/TiptapComposer.js.map +1 -1
  24. package/dist/client/context-xray/ContextMeter.d.ts +2 -1
  25. package/dist/client/context-xray/ContextMeter.d.ts.map +1 -1
  26. package/dist/client/context-xray/ContextMeter.js +19 -25
  27. package/dist/client/context-xray/ContextMeter.js.map +1 -1
  28. package/dist/client/context-xray/ContextXRayPanel.d.ts +1 -3
  29. package/dist/client/context-xray/ContextXRayPanel.d.ts.map +1 -1
  30. package/dist/client/context-xray/ContextXRayPanel.js +27 -24
  31. package/dist/client/context-xray/ContextXRayPanel.js.map +1 -1
  32. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  33. package/dist/client/conversation/AgentConversation.js +2 -1
  34. package/dist/client/conversation/AgentConversation.js.map +1 -1
  35. package/dist/client/sse-event-processor.d.ts +1 -0
  36. package/dist/client/sse-event-processor.d.ts.map +1 -1
  37. package/dist/client/sse-event-processor.js +62 -15
  38. package/dist/client/sse-event-processor.js.map +1 -1
  39. package/dist/client/tool-display.d.ts +4 -0
  40. package/dist/client/tool-display.d.ts.map +1 -0
  41. package/dist/client/tool-display.js +28 -0
  42. package/dist/client/tool-display.js.map +1 -0
  43. package/dist/client/use-chat-threads.d.ts.map +1 -1
  44. package/dist/client/use-chat-threads.js +40 -31
  45. package/dist/client/use-chat-threads.js.map +1 -1
  46. package/dist/client/use-external-value.d.ts.map +1 -1
  47. package/dist/client/use-external-value.js +14 -7
  48. package/dist/client/use-external-value.js.map +1 -1
  49. package/dist/extensions/html-shell.d.ts +3 -2
  50. package/dist/extensions/html-shell.d.ts.map +1 -1
  51. package/dist/extensions/html-shell.js +12 -2
  52. package/dist/extensions/html-shell.js.map +1 -1
  53. package/dist/extensions/routes.js +2 -7
  54. package/dist/extensions/routes.js.map +1 -1
  55. package/dist/server/core-routes-plugin.js +2 -2
  56. package/dist/server/core-routes-plugin.js.map +1 -1
  57. package/dist/server/security-headers.d.ts +16 -19
  58. package/dist/server/security-headers.d.ts.map +1 -1
  59. package/dist/server/security-headers.js +24 -25
  60. package/dist/server/security-headers.js.map +1 -1
  61. package/docs/content/external-agents.md +1 -1
  62. package/package.json +1 -1
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Sets a baseline set of "no-brainer" security headers on every framework HTTP
5
5
  * response. These headers are layered defenses: each one mitigates a specific
6
- * class of attack, and together they harden the surface against clickjacking,
7
- * MIME-sniffing, referrer leakage, mixed-content downgrades, and cross-origin
8
- * window/embed access.
6
+ * class of attack, and together they harden the surface against MIME-sniffing,
7
+ * referrer leakage, mixed-content downgrades, and cross-origin window/embed
8
+ * access.
9
9
  *
10
10
  * The headers we emit:
11
11
  *
@@ -16,15 +16,6 @@
16
16
  * - `X-Content-Type-Options: nosniff` — disables browser MIME sniffing so
17
17
  * a tool /render route serving user-authored HTML can't be misinterpreted
18
18
  * as some other content type by a clever Accept header.
19
- * - `X-Frame-Options: DENY` — prevents the entire app from being iframed by
20
- * other origins (clickjacking the agent chat, booking pages, etc.). The
21
- * tool /render endpoint and any other route that legitimately needs to be
22
- * embedded in the same-origin app shell can opt out by setting its own
23
- * header inside the route handler — h3's `setResponseHeader` overwrites,
24
- * so a route emitting `SAMEORIGIN` wins over our middleware default.
25
- * We skip this header entirely in dev (NODE_ENV !== "production") so the
26
- * desktop app's local dev frame (localhost:3334) can iframe templates
27
- * running on other localhost ports (e.g. mail at 8085).
28
19
  * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query
29
20
  * from outbound Referer headers when the request crosses origin, so a
30
21
  * public-share viewer's outbound link clicks never leak the share token.
@@ -36,25 +27,31 @@
36
27
  * - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so
37
28
  * a popup-window opener reference can't read or modify our document.
38
29
  * - `Cross-Origin-Embedder-Policy: require-corp` — emitted only for
39
- * validated MCP embed-session page loads. COEP hosts such as Claude's MCP
40
- * Apps proxy require framed cross-origin documents to opt in explicitly.
30
+ * validated MCP embed-session page loads and browser iframe navigations.
31
+ * COEP hosts such as Claude's MCP Apps proxy require framed cross-origin
32
+ * documents to opt in explicitly.
41
33
  * - `Cross-Origin-Resource-Policy: same-site` — prevents other origins from
42
34
  * embedding our endpoints as `<img>` / `<script>` / `<audio>`, blocking
43
35
  * the simplest data-leak chain when combined with auth cookies. Validated
44
- * MCP embed-session page loads use `cross-origin` so COEP hosts such as
45
- * Claude's MCP Apps proxy can frame the short-lived app document.
36
+ * MCP embed-session page loads and browser iframe navigations use
37
+ * `cross-origin` so COEP hosts can frame app documents.
46
38
  *
47
39
  * NOTE: `Cross-Origin-Embedder-Policy` is NOT set by default because it
48
40
  * requires every embedded subresource to opt in via CORP/CORS, which would
49
41
  * break Builder's iframe editor and template embed use cases. COOP + CORP
50
42
  * without COEP gives us most of the protection on normal responses; COEP is
51
- * only added for validated MCP embed-session page loads (see above).
43
+ * only added for validated MCP embed-session page loads and browser iframe
44
+ * navigations (see above).
45
+ *
46
+ * NOTE: `X-Frame-Options` is intentionally not set globally. Agent-native apps
47
+ * are expected to run inside iframe hosts such as Builder, Design, and MCP app
48
+ * shells. Routes that render especially sensitive iframe-only documents should
49
+ * set their own route-specific CSP / frame policy.
52
50
  */
53
51
  /**
54
52
  * Create the security-headers h3 middleware. Mount this BEFORE other route
55
53
  * handlers so the headers are present on every response (including 4xx/5xx
56
- * error pages). Route handlers that need to relax a specific header (e.g.
57
- * `X-Frame-Options: SAMEORIGIN` on the tool render route) can call
54
+ * error pages). Route handlers that need to tighten a specific header can call
58
55
  * `setResponseHeader` after this runs — the latest write wins.
59
56
  */
60
57
  export declare function createSecurityHeadersMiddleware(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, any>;
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AA2CH;;;;;;GAMG;AACH,wBAAgB,+BAA+B,8EA6C9C"}
1
+ {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AA+CH;;;;;GAKG;AACH,wBAAgB,+BAA+B,8EA4C9C"}
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Sets a baseline set of "no-brainer" security headers on every framework HTTP
5
5
  * response. These headers are layered defenses: each one mitigates a specific
6
- * class of attack, and together they harden the surface against clickjacking,
7
- * MIME-sniffing, referrer leakage, mixed-content downgrades, and cross-origin
8
- * window/embed access.
6
+ * class of attack, and together they harden the surface against MIME-sniffing,
7
+ * referrer leakage, mixed-content downgrades, and cross-origin window/embed
8
+ * access.
9
9
  *
10
10
  * The headers we emit:
11
11
  *
@@ -16,15 +16,6 @@
16
16
  * - `X-Content-Type-Options: nosniff` — disables browser MIME sniffing so
17
17
  * a tool /render route serving user-authored HTML can't be misinterpreted
18
18
  * as some other content type by a clever Accept header.
19
- * - `X-Frame-Options: DENY` — prevents the entire app from being iframed by
20
- * other origins (clickjacking the agent chat, booking pages, etc.). The
21
- * tool /render endpoint and any other route that legitimately needs to be
22
- * embedded in the same-origin app shell can opt out by setting its own
23
- * header inside the route handler — h3's `setResponseHeader` overwrites,
24
- * so a route emitting `SAMEORIGIN` wins over our middleware default.
25
- * We skip this header entirely in dev (NODE_ENV !== "production") so the
26
- * desktop app's local dev frame (localhost:3334) can iframe templates
27
- * running on other localhost ports (e.g. mail at 8085).
28
19
  * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query
29
20
  * from outbound Referer headers when the request crosses origin, so a
30
21
  * public-share viewer's outbound link clicks never leak the share token.
@@ -36,19 +27,26 @@
36
27
  * - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so
37
28
  * a popup-window opener reference can't read or modify our document.
38
29
  * - `Cross-Origin-Embedder-Policy: require-corp` — emitted only for
39
- * validated MCP embed-session page loads. COEP hosts such as Claude's MCP
40
- * Apps proxy require framed cross-origin documents to opt in explicitly.
30
+ * validated MCP embed-session page loads and browser iframe navigations.
31
+ * COEP hosts such as Claude's MCP Apps proxy require framed cross-origin
32
+ * documents to opt in explicitly.
41
33
  * - `Cross-Origin-Resource-Policy: same-site` — prevents other origins from
42
34
  * embedding our endpoints as `<img>` / `<script>` / `<audio>`, blocking
43
35
  * the simplest data-leak chain when combined with auth cookies. Validated
44
- * MCP embed-session page loads use `cross-origin` so COEP hosts such as
45
- * Claude's MCP Apps proxy can frame the short-lived app document.
36
+ * MCP embed-session page loads and browser iframe navigations use
37
+ * `cross-origin` so COEP hosts can frame app documents.
46
38
  *
47
39
  * NOTE: `Cross-Origin-Embedder-Policy` is NOT set by default because it
48
40
  * requires every embedded subresource to opt in via CORP/CORS, which would
49
41
  * break Builder's iframe editor and template embed use cases. COOP + CORP
50
42
  * without COEP gives us most of the protection on normal responses; COEP is
51
- * only added for validated MCP embed-session page loads (see above).
43
+ * only added for validated MCP embed-session page loads and browser iframe
44
+ * navigations (see above).
45
+ *
46
+ * NOTE: `X-Frame-Options` is intentionally not set globally. Agent-native apps
47
+ * are expected to run inside iframe hosts such as Builder, Design, and MCP app
48
+ * shells. Routes that render especially sensitive iframe-only documents should
49
+ * set their own route-specific CSP / frame policy.
52
50
  */
53
51
  import { defineEventHandler, getHeader, setResponseHeader } from "h3";
54
52
  import { requestHasEmbedAuthMarker } from "./embed-session.js";
@@ -82,30 +80,31 @@ function isMcpEndpointRequest(event) {
82
80
  String(event?.node?.req?.url ?? event?.path ?? "/").split("?")[0];
83
81
  return (pathname === "/_agent-native/mcp" || pathname.endsWith("/_agent-native/mcp"));
84
82
  }
83
+ function isIframeNavigationRequest(event) {
84
+ return getHeader(event, "sec-fetch-dest") === "iframe";
85
+ }
85
86
  /**
86
87
  * Create the security-headers h3 middleware. Mount this BEFORE other route
87
88
  * handlers so the headers are present on every response (including 4xx/5xx
88
- * error pages). Route handlers that need to relax a specific header (e.g.
89
- * `X-Frame-Options: SAMEORIGIN` on the tool render route) can call
89
+ * error pages). Route handlers that need to tighten a specific header can call
90
90
  * `setResponseHeader` after this runs — the latest write wins.
91
91
  */
92
92
  export function createSecurityHeadersMiddleware() {
93
- const isProduction = process.env.NODE_ENV === "production";
94
93
  return defineEventHandler((event) => {
95
94
  const embedFrameRequest = requestHasEmbedAuthMarker(event);
96
95
  const mcpEndpointRequest = isMcpEndpointRequest(event);
96
+ const iframeNavigationRequest = isIframeNavigationRequest(event);
97
97
  const requestOrigin = getHeader(event, "origin");
98
98
  setResponseHeader(event, "X-Content-Type-Options", "nosniff");
99
- if (isProduction && !embedFrameRequest) {
100
- setResponseHeader(event, "X-Frame-Options", "DENY");
101
- }
102
99
  setResponseHeader(event, "Referrer-Policy", embedFrameRequest ? "no-referrer" : "strict-origin-when-cross-origin");
103
100
  setResponseHeader(event, "Permissions-Policy", PERMISSIONS_POLICY);
104
101
  setResponseHeader(event, "Cross-Origin-Opener-Policy", "same-origin");
105
- if (embedFrameRequest) {
102
+ if (embedFrameRequest || iframeNavigationRequest) {
106
103
  setResponseHeader(event, "Cross-Origin-Embedder-Policy", "require-corp");
107
104
  }
108
- setResponseHeader(event, "Cross-Origin-Resource-Policy", embedFrameRequest || mcpEndpointRequest ? "cross-origin" : "same-site");
105
+ setResponseHeader(event, "Cross-Origin-Resource-Policy", embedFrameRequest || mcpEndpointRequest || iframeNavigationRequest
106
+ ? "cross-origin"
107
+ : "same-site");
109
108
  if (embedFrameRequest && isMcpEmbedCorsOrigin(requestOrigin)) {
110
109
  setResponseHeader(event, "Access-Control-Allow-Origin", requestOrigin);
111
110
  setResponseHeader(event, "Vary", "Origin");
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,IAAI,CAAC;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EACL,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,gCAAgC,CAAC;AAExC,MAAM,IAAI,GAAG,8CAA8C,CAAC;AAC5D,MAAM,kBAAkB,GACtB,mEAAmE,CAAC;AAEtE;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAU;IAChC,MAAM,GAAG,GACP,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,mBAAmB,CAAC;QAChD,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC;IAC7C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,OAAO;QACjE,OAAO,IAAI,CAAC;IACd,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1D,uDAAuD;IACvD,MAAM,KAAK,GAAG,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC;IACnC,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACpC,2DAA2D;IAC3D,IAAI,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAU;IACtC,MAAM,QAAQ,GACZ,KAAK,EAAE,GAAG,EAAE,QAAQ;QACpB,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,KAAK,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,CACL,QAAQ,KAAK,oBAAoB,IAAI,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAC7E,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,+BAA+B;IAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;IAC3D,OAAO,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;QAClC,MAAM,iBAAiB,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;QAC3D,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACvD,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACjD,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,EAAE,SAAS,CAAC,CAAC;QAC9D,IAAI,YAAY,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvC,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC;QACD,iBAAiB,CACf,KAAK,EACL,iBAAiB,EACjB,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,iCAAiC,CACtE,CAAC;QACF,iBAAiB,CAAC,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,CAAC,CAAC;QACnE,iBAAiB,CAAC,KAAK,EAAE,4BAA4B,EAAE,aAAa,CAAC,CAAC;QACtE,IAAI,iBAAiB,EAAE,CAAC;YACtB,iBAAiB,CAAC,KAAK,EAAE,8BAA8B,EAAE,cAAc,CAAC,CAAC;QAC3E,CAAC;QACD,iBAAiB,CACf,KAAK,EACL,8BAA8B,EAC9B,iBAAiB,IAAI,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CACvE,CAAC;QACF,IAAI,iBAAiB,IAAI,oBAAoB,CAAC,aAAa,CAAC,EAAE,CAAC;YAC7D,iBAAiB,CAAC,KAAK,EAAE,6BAA6B,EAAE,aAAa,CAAC,CAAC;YACvE,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YAC3C,iBAAiB,CACf,KAAK,EACL,8BAA8B,EAC9B,wCAAwC,CACzC,CAAC;YACF,iBAAiB,CACf,KAAK,EACL,8BAA8B,EAC9B,4BAA4B,CAC7B,CAAC;QACJ,CAAC;QACD,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,iBAAiB,CAAC,KAAK,EAAE,2BAA2B,EAAE,IAAI,CAAC,CAAC;QAC9D,CAAC;QACD,2EAA2E;QAC3E,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Security response headers middleware.\n *\n * Sets a baseline set of \"no-brainer\" security headers on every framework HTTP\n * response. These headers are layered defenses: each one mitigates a specific\n * class of attack, and together they harden the surface against clickjacking,\n * MIME-sniffing, referrer leakage, mixed-content downgrades, and cross-origin\n * window/embed access.\n *\n * The headers we emit:\n *\n * - `Strict-Transport-Security` — forces HTTPS for the browser's lifetime\n * of the cached value, preventing SSL-strip MITM. Only emitted when the\n * request scheme is `https` (we don't want to break local-dev HTTP, and\n * emitting HSTS over HTTP is a no-op per the spec but causes confusion).\n * - `X-Content-Type-Options: nosniff` — disables browser MIME sniffing so\n * a tool /render route serving user-authored HTML can't be misinterpreted\n * as some other content type by a clever Accept header.\n * - `X-Frame-Options: DENY` — prevents the entire app from being iframed by\n * other origins (clickjacking the agent chat, booking pages, etc.). The\n * tool /render endpoint and any other route that legitimately needs to be\n * embedded in the same-origin app shell can opt out by setting its own\n * header inside the route handler — h3's `setResponseHeader` overwrites,\n * so a route emitting `SAMEORIGIN` wins over our middleware default.\n * We skip this header entirely in dev (NODE_ENV !== \"production\") so the\n * desktop app's local dev frame (localhost:3334) can iframe templates\n * running on other localhost ports (e.g. mail at 8085).\n * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query\n * from outbound Referer headers when the request crosses origin, so a\n * public-share viewer's outbound link clicks never leak the share token.\n * - `Permissions-Policy: camera=(), microphone=(self), geolocation=(),\n * screen-wake-lock=()` — allows the app shell to request microphone access\n * for composer dictation while keeping camera/location/wake-lock blocked\n * by default. Templates that need broader media capture for recording UI\n * override this on their own routes.\n * - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so\n * a popup-window opener reference can't read or modify our document.\n * - `Cross-Origin-Embedder-Policy: require-corp` — emitted only for\n * validated MCP embed-session page loads. COEP hosts such as Claude's MCP\n * Apps proxy require framed cross-origin documents to opt in explicitly.\n * - `Cross-Origin-Resource-Policy: same-site` — prevents other origins from\n * embedding our endpoints as `<img>` / `<script>` / `<audio>`, blocking\n * the simplest data-leak chain when combined with auth cookies. Validated\n * MCP embed-session page loads use `cross-origin` so COEP hosts such as\n * Claude's MCP Apps proxy can frame the short-lived app document.\n *\n * NOTE: `Cross-Origin-Embedder-Policy` is NOT set by default because it\n * requires every embedded subresource to opt in via CORP/CORS, which would\n * break Builder's iframe editor and template embed use cases. COOP + CORP\n * without COEP gives us most of the protection on normal responses; COEP is\n * only added for validated MCP embed-session page loads (see above).\n */\n\nimport { defineEventHandler, getHeader, setResponseHeader } from \"h3\";\nimport { requestHasEmbedAuthMarker } from \"./embed-session.js\";\nimport {\n isMcpEmbedCorsOrigin,\n MCP_EMBED_CORS_ALLOW_HEADERS,\n} from \"../shared/mcp-embed-headers.js\";\n\nconst HSTS = \"max-age=31536000; includeSubDomains; preload\";\nconst PERMISSIONS_POLICY =\n \"camera=(), microphone=(self), geolocation=(), screen-wake-lock=()\";\n\n/**\n * Returns true when the request was received over HTTPS. We trust both the\n * underlying connection (when the server is terminating TLS itself) and the\n * `x-forwarded-proto` header (set by Netlify, Vercel, Cloudflare, and any\n * other reverse proxy that fronts the framework).\n */\nfunction isHttpsRequest(event: any): boolean {\n const xfp =\n event?.node?.req?.headers?.[\"x-forwarded-proto\"] ??\n event?.headers?.get?.(\"x-forwarded-proto\");\n if (typeof xfp === \"string\" && xfp.split(\",\")[0].trim() === \"https\")\n return true;\n if (Array.isArray(xfp) && xfp[0] === \"https\") return true;\n // h3 sets `event.url.protocol` to \"http:\" or \"https:\".\n const proto = event?.url?.protocol;\n if (proto === \"https:\") return true;\n // Direct Node `req.connection.encrypted` (older runtimes).\n if (event?.node?.req?.connection?.encrypted) return true;\n return false;\n}\n\nfunction isMcpEndpointRequest(event: any): boolean {\n const pathname =\n event?.url?.pathname ??\n String(event?.node?.req?.url ?? event?.path ?? \"/\").split(\"?\")[0];\n return (\n pathname === \"/_agent-native/mcp\" || pathname.endsWith(\"/_agent-native/mcp\")\n );\n}\n\n/**\n * Create the security-headers h3 middleware. Mount this BEFORE other route\n * handlers so the headers are present on every response (including 4xx/5xx\n * error pages). Route handlers that need to relax a specific header (e.g.\n * `X-Frame-Options: SAMEORIGIN` on the tool render route) can call\n * `setResponseHeader` after this runs — the latest write wins.\n */\nexport function createSecurityHeadersMiddleware() {\n const isProduction = process.env.NODE_ENV === \"production\";\n return defineEventHandler((event) => {\n const embedFrameRequest = requestHasEmbedAuthMarker(event);\n const mcpEndpointRequest = isMcpEndpointRequest(event);\n const requestOrigin = getHeader(event, \"origin\");\n setResponseHeader(event, \"X-Content-Type-Options\", \"nosniff\");\n if (isProduction && !embedFrameRequest) {\n setResponseHeader(event, \"X-Frame-Options\", \"DENY\");\n }\n setResponseHeader(\n event,\n \"Referrer-Policy\",\n embedFrameRequest ? \"no-referrer\" : \"strict-origin-when-cross-origin\",\n );\n setResponseHeader(event, \"Permissions-Policy\", PERMISSIONS_POLICY);\n setResponseHeader(event, \"Cross-Origin-Opener-Policy\", \"same-origin\");\n if (embedFrameRequest) {\n setResponseHeader(event, \"Cross-Origin-Embedder-Policy\", \"require-corp\");\n }\n setResponseHeader(\n event,\n \"Cross-Origin-Resource-Policy\",\n embedFrameRequest || mcpEndpointRequest ? \"cross-origin\" : \"same-site\",\n );\n if (embedFrameRequest && isMcpEmbedCorsOrigin(requestOrigin)) {\n setResponseHeader(event, \"Access-Control-Allow-Origin\", requestOrigin);\n setResponseHeader(event, \"Vary\", \"Origin\");\n setResponseHeader(\n event,\n \"Access-Control-Allow-Methods\",\n \"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS\",\n );\n setResponseHeader(\n event,\n \"Access-Control-Allow-Headers\",\n MCP_EMBED_CORS_ALLOW_HEADERS,\n );\n }\n if (isHttpsRequest(event)) {\n setResponseHeader(event, \"Strict-Transport-Security\", HSTS);\n }\n // Continue to the next handler — we only set headers, don't return a body.\n return undefined;\n });\n}\n"]}
1
+ {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Security response headers middleware.\n *\n * Sets a baseline set of \"no-brainer\" security headers on every framework HTTP\n * response. These headers are layered defenses: each one mitigates a specific\n * class of attack, and together they harden the surface against MIME-sniffing,\n * referrer leakage, mixed-content downgrades, and cross-origin window/embed\n * access.\n *\n * The headers we emit:\n *\n * - `Strict-Transport-Security` — forces HTTPS for the browser's lifetime\n * of the cached value, preventing SSL-strip MITM. Only emitted when the\n * request scheme is `https` (we don't want to break local-dev HTTP, and\n * emitting HSTS over HTTP is a no-op per the spec but causes confusion).\n * - `X-Content-Type-Options: nosniff` — disables browser MIME sniffing so\n * a tool /render route serving user-authored HTML can't be misinterpreted\n * as some other content type by a clever Accept header.\n * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query\n * from outbound Referer headers when the request crosses origin, so a\n * public-share viewer's outbound link clicks never leak the share token.\n * - `Permissions-Policy: camera=(), microphone=(self), geolocation=(),\n * screen-wake-lock=()` — allows the app shell to request microphone access\n * for composer dictation while keeping camera/location/wake-lock blocked\n * by default. Templates that need broader media capture for recording UI\n * override this on their own routes.\n * - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so\n * a popup-window opener reference can't read or modify our document.\n * - `Cross-Origin-Embedder-Policy: require-corp` — emitted only for\n * validated MCP embed-session page loads and browser iframe navigations.\n * COEP hosts such as Claude's MCP Apps proxy require framed cross-origin\n * documents to opt in explicitly.\n * - `Cross-Origin-Resource-Policy: same-site` — prevents other origins from\n * embedding our endpoints as `<img>` / `<script>` / `<audio>`, blocking\n * the simplest data-leak chain when combined with auth cookies. Validated\n * MCP embed-session page loads and browser iframe navigations use\n * `cross-origin` so COEP hosts can frame app documents.\n *\n * NOTE: `Cross-Origin-Embedder-Policy` is NOT set by default because it\n * requires every embedded subresource to opt in via CORP/CORS, which would\n * break Builder's iframe editor and template embed use cases. COOP + CORP\n * without COEP gives us most of the protection on normal responses; COEP is\n * only added for validated MCP embed-session page loads and browser iframe\n * navigations (see above).\n *\n * NOTE: `X-Frame-Options` is intentionally not set globally. Agent-native apps\n * are expected to run inside iframe hosts such as Builder, Design, and MCP app\n * shells. Routes that render especially sensitive iframe-only documents should\n * set their own route-specific CSP / frame policy.\n */\n\nimport { defineEventHandler, getHeader, setResponseHeader } from \"h3\";\nimport { requestHasEmbedAuthMarker } from \"./embed-session.js\";\nimport {\n isMcpEmbedCorsOrigin,\n MCP_EMBED_CORS_ALLOW_HEADERS,\n} from \"../shared/mcp-embed-headers.js\";\n\nconst HSTS = \"max-age=31536000; includeSubDomains; preload\";\nconst PERMISSIONS_POLICY =\n \"camera=(), microphone=(self), geolocation=(), screen-wake-lock=()\";\n\n/**\n * Returns true when the request was received over HTTPS. We trust both the\n * underlying connection (when the server is terminating TLS itself) and the\n * `x-forwarded-proto` header (set by Netlify, Vercel, Cloudflare, and any\n * other reverse proxy that fronts the framework).\n */\nfunction isHttpsRequest(event: any): boolean {\n const xfp =\n event?.node?.req?.headers?.[\"x-forwarded-proto\"] ??\n event?.headers?.get?.(\"x-forwarded-proto\");\n if (typeof xfp === \"string\" && xfp.split(\",\")[0].trim() === \"https\")\n return true;\n if (Array.isArray(xfp) && xfp[0] === \"https\") return true;\n // h3 sets `event.url.protocol` to \"http:\" or \"https:\".\n const proto = event?.url?.protocol;\n if (proto === \"https:\") return true;\n // Direct Node `req.connection.encrypted` (older runtimes).\n if (event?.node?.req?.connection?.encrypted) return true;\n return false;\n}\n\nfunction isMcpEndpointRequest(event: any): boolean {\n const pathname =\n event?.url?.pathname ??\n String(event?.node?.req?.url ?? event?.path ?? \"/\").split(\"?\")[0];\n return (\n pathname === \"/_agent-native/mcp\" || pathname.endsWith(\"/_agent-native/mcp\")\n );\n}\n\nfunction isIframeNavigationRequest(event: any): boolean {\n return getHeader(event, \"sec-fetch-dest\") === \"iframe\";\n}\n\n/**\n * Create the security-headers h3 middleware. Mount this BEFORE other route\n * handlers so the headers are present on every response (including 4xx/5xx\n * error pages). Route handlers that need to tighten a specific header can call\n * `setResponseHeader` after this runs — the latest write wins.\n */\nexport function createSecurityHeadersMiddleware() {\n return defineEventHandler((event) => {\n const embedFrameRequest = requestHasEmbedAuthMarker(event);\n const mcpEndpointRequest = isMcpEndpointRequest(event);\n const iframeNavigationRequest = isIframeNavigationRequest(event);\n const requestOrigin = getHeader(event, \"origin\");\n setResponseHeader(event, \"X-Content-Type-Options\", \"nosniff\");\n setResponseHeader(\n event,\n \"Referrer-Policy\",\n embedFrameRequest ? \"no-referrer\" : \"strict-origin-when-cross-origin\",\n );\n setResponseHeader(event, \"Permissions-Policy\", PERMISSIONS_POLICY);\n setResponseHeader(event, \"Cross-Origin-Opener-Policy\", \"same-origin\");\n if (embedFrameRequest || iframeNavigationRequest) {\n setResponseHeader(event, \"Cross-Origin-Embedder-Policy\", \"require-corp\");\n }\n setResponseHeader(\n event,\n \"Cross-Origin-Resource-Policy\",\n embedFrameRequest || mcpEndpointRequest || iframeNavigationRequest\n ? \"cross-origin\"\n : \"same-site\",\n );\n if (embedFrameRequest && isMcpEmbedCorsOrigin(requestOrigin)) {\n setResponseHeader(event, \"Access-Control-Allow-Origin\", requestOrigin);\n setResponseHeader(event, \"Vary\", \"Origin\");\n setResponseHeader(\n event,\n \"Access-Control-Allow-Methods\",\n \"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS\",\n );\n setResponseHeader(\n event,\n \"Access-Control-Allow-Headers\",\n MCP_EMBED_CORS_ALLOW_HEADERS,\n );\n }\n if (isHttpsRequest(event)) {\n setResponseHeader(event, \"Strict-Transport-Security\", HSTS);\n }\n // Continue to the next handler — we only set headers, don't return a body.\n return undefined;\n });\n}\n"]}
@@ -616,7 +616,7 @@ If `connect dev` cannot infer your local owner identity from an existing connect
616
616
 
617
617
  The standard OAuth path never exposes tokens to MCP Apps: the host stores OAuth access/refresh tokens and mediates tool calls and `resources/read` over the authenticated MCP connection. Embedded iframes receive app data and tool results, not bearer secrets.
618
618
 
619
- Full-app embeds also avoid handing the MCP bearer token to the browser. The MCP caller mints a one-time embed ticket in SQL; the iframe launch route consumes it and sets a short-lived, iframe-safe browser session cookie. The landing URL carries a temporary `__an_embed_token` query param only long enough for the client to capture it, remove it from the address bar, and attach it to same-origin `fetch` calls when third-party cookies are blocked. Embed sessions are route-scoped; app fetches include the current embedded target, and the server rejects token reuse outside the minted route. Production `X-Frame-Options: DENY` stays in place for normal page loads and is omitted only when that embed session marker is present.
619
+ Full-app embeds also avoid handing the MCP bearer token to the browser. The MCP caller mints a one-time embed ticket in SQL; the iframe launch route consumes it and sets a short-lived, iframe-safe browser session cookie. The landing URL carries a temporary `__an_embed_token` query param only long enough for the client to capture it, remove it from the address bar, and attach it to same-origin `fetch` calls when third-party cookies are blocked. Embed sessions are route-scoped; app fetches include the current embedded target, and the server rejects token reuse outside the minted route. App pages intentionally do not emit `X-Frame-Options` or CSP `frame-ancestors`, so Builder, Design, and MCP app hosts can iframe them. Browser iframe navigations also opt into COEP/CORP when needed for cross-origin isolated hosts.
620
620
 
621
621
  The fallback hosted `connect` flow never copies the deployment's shared secret. Instead:
622
622
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.35.3",
3
+ "version": "0.36.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"