@agent-native/core 0.206.0-nightly-20261009124314 → 0.206.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/action.d.ts +7 -1
- package/dist/app-config/extension-display-sources.d.ts +31 -0
- package/dist/app-config/extension-display-sources.js +45 -0
- package/dist/app-config/extensions.d.ts +7 -0
- package/dist/app-config/extensions.js +39 -0
- package/dist/app-config/index.d.ts +1 -0
- package/dist/app-config/index.js +1 -0
- package/dist/app-config/schema.d.ts +4 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/embed-auth.d.ts +2 -0
- package/dist/client/embed-auth.js +160 -12
- package/dist/client/host/index.d.ts +2 -2
- package/dist/client/host/index.js +2 -2
- package/dist/client/mcp-app-host.d.ts +1 -0
- package/dist/client/mcp-app-host.js +4 -1
- package/dist/client/session-replay.js +70 -6
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.js +2 -0
- package/dist/db/drizzle-config.js +2 -0
- package/dist/event-bus/bus.d.ts +6 -0
- package/dist/event-bus/bus.js +24 -1
- package/dist/event-bus/index.d.ts +1 -1
- package/dist/event-bus/index.js +1 -1
- package/dist/extensions/html-shell.d.ts +35 -2
- package/dist/extensions/html-shell.js +62 -5
- package/dist/extensions/iframe-csp.d.ts +20 -0
- package/dist/extensions/iframe-csp.js +30 -0
- package/dist/extensions/routes.js +17 -5
- package/dist/mcp/build-server.d.ts +15 -0
- package/dist/mcp/build-server.js +307 -91
- package/dist/mcp/connect-route.js +1 -0
- package/dist/mcp/embed-app.js +291 -31
- package/dist/mcp/oauth-route.js +3 -0
- package/dist/mcp/oauth-store.d.ts +2 -1
- package/dist/mcp/oauth-store.js +5 -6
- package/dist/mcp/oauth-token.d.ts +5 -0
- package/dist/mcp/oauth-token.js +17 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +2 -2
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/index.d.ts +2 -2
- package/dist/org/workspace-app-access-request.d.ts +8 -0
- package/dist/org/workspace-app-access-request.js +20 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- package/dist/resources/store.d.ts +22 -0
- package/dist/resources/store.js +88 -5
- package/dist/secrets/routes.d.ts +11 -11
- package/dist/server/action-routes.d.ts +2 -0
- package/dist/server/action-routes.js +187 -31
- package/dist/server/agent-chat/mcp-options.d.ts +3 -1
- package/dist/server/agent-chat-plugin.js +26 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/auth.js +11 -20
- package/dist/server/embed-route.js +5 -3
- package/dist/server/embed-session.d.ts +30 -1
- package/dist/server/embed-session.js +376 -30
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +25 -1
- package/dist/shared/embed-auth.d.ts +74 -0
- package/dist/shared/embed-auth.js +379 -10
- package/dist/triggers/actions/manage-automation.d.ts +2 -2
- package/dist/triggers/dispatcher.d.ts +11 -0
- package/dist/triggers/dispatcher.js +239 -50
- package/dist/triggers/index.d.ts +1 -1
- package/dist/triggers/index.js +1 -1
- package/docs/content/template-design-developers.mdx +1 -0
- package/package.json +4 -4
package/dist/collab/routes.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
13
13
|
};
|
|
14
14
|
}>>;
|
|
15
15
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error: string;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
package/dist/db/client.js
CHANGED
|
@@ -156,7 +156,9 @@ export function pgliteDataDirFromUrl(url) {
|
|
|
156
156
|
if (!dataDir || dataDir === "/")
|
|
157
157
|
return "./data/pglite";
|
|
158
158
|
if (dataDir === "memory" ||
|
|
159
|
+
dataDir === "memory:" ||
|
|
159
160
|
dataDir === "/memory" ||
|
|
161
|
+
dataDir === "/memory:" ||
|
|
160
162
|
dataDir === ":memory:" ||
|
|
161
163
|
dataDir === "/:memory:" ||
|
|
162
164
|
dataDir === "memory://") {
|
|
@@ -20,7 +20,9 @@ function pgliteDataDirFromUrl(url) {
|
|
|
20
20
|
if (!dataDir || dataDir === "/")
|
|
21
21
|
return "./data/pglite";
|
|
22
22
|
if (dataDir === "memory" ||
|
|
23
|
+
dataDir === "memory:" ||
|
|
23
24
|
dataDir === "/memory" ||
|
|
25
|
+
dataDir === "/memory:" ||
|
|
24
26
|
dataDir === ":memory:" ||
|
|
25
27
|
dataDir === "/:memory:" ||
|
|
26
28
|
dataDir === "memory://") {
|
package/dist/event-bus/bus.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { EventMeta } from "./types.js";
|
|
2
2
|
type Handler = (payload: unknown, meta: EventMeta) => void | Promise<void>;
|
|
3
|
+
type AnyEventHandler = (event: string, payload: unknown, meta: EventMeta) => void | Promise<void>;
|
|
3
4
|
export declare function subscribe(event: string, handler: Handler): string;
|
|
5
|
+
/**
|
|
6
|
+
* Subscribe to every emitted event. For a subscriber whose interest is stored
|
|
7
|
+
* data it should not read until an event actually arrives.
|
|
8
|
+
*/
|
|
9
|
+
export declare function subscribeAll(handler: AnyEventHandler): string;
|
|
4
10
|
export declare function unsubscribe(id: string): boolean;
|
|
5
11
|
export declare function emit(event: string, payload: unknown, meta?: Partial<EventMeta>): void;
|
|
6
12
|
/** Emit an event and wait until every subscriber has accepted it. */
|
package/dist/event-bus/bus.js
CHANGED
|
@@ -11,6 +11,11 @@ function getBus() {
|
|
|
11
11
|
}
|
|
12
12
|
return g[BUS_KEY];
|
|
13
13
|
}
|
|
14
|
+
function getAnyEventSubscriptions() {
|
|
15
|
+
const bus = getBus();
|
|
16
|
+
bus.anyEventSubscriptions ??= new Map();
|
|
17
|
+
return bus.anyEventSubscriptions;
|
|
18
|
+
}
|
|
14
19
|
export function subscribe(event, handler) {
|
|
15
20
|
if (typeof event !== "string" || !event) {
|
|
16
21
|
throw new Error("subscribe: event name is required");
|
|
@@ -24,7 +29,21 @@ export function subscribe(event, handler) {
|
|
|
24
29
|
bus.emitter.on(event, handler);
|
|
25
30
|
return id;
|
|
26
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Subscribe to every emitted event. For a subscriber whose interest is stored
|
|
34
|
+
* data it should not read until an event actually arrives.
|
|
35
|
+
*/
|
|
36
|
+
export function subscribeAll(handler) {
|
|
37
|
+
if (typeof handler !== "function") {
|
|
38
|
+
throw new Error("subscribeAll: handler must be a function");
|
|
39
|
+
}
|
|
40
|
+
const id = randomUUID();
|
|
41
|
+
getAnyEventSubscriptions().set(id, handler);
|
|
42
|
+
return id;
|
|
43
|
+
}
|
|
27
44
|
export function unsubscribe(id) {
|
|
45
|
+
if (getAnyEventSubscriptions().delete(id))
|
|
46
|
+
return true;
|
|
28
47
|
const bus = getBus();
|
|
29
48
|
const sub = bus.subscriptions.get(id);
|
|
30
49
|
if (!sub)
|
|
@@ -95,7 +114,10 @@ function prepareDispatch(event, payload, meta, throwOnInvalid) {
|
|
|
95
114
|
emittedAt: meta?.emittedAt ?? new Date().toISOString(),
|
|
96
115
|
owner: meta?.owner,
|
|
97
116
|
};
|
|
98
|
-
const listeners =
|
|
117
|
+
const listeners = [
|
|
118
|
+
...bus.emitter.listeners(event),
|
|
119
|
+
...[...getAnyEventSubscriptions().values()].map((handler) => (eventPayload, eventMeta) => handler(event, eventPayload, eventMeta)),
|
|
120
|
+
];
|
|
99
121
|
return { payload: validated, meta: fullMeta, listeners };
|
|
100
122
|
}
|
|
101
123
|
export function listSubscriptions(event) {
|
|
@@ -112,4 +134,5 @@ export function __resetEventBus() {
|
|
|
112
134
|
const bus = getBus();
|
|
113
135
|
bus.emitter.removeAllListeners();
|
|
114
136
|
bus.subscriptions.clear();
|
|
137
|
+
getAnyEventSubscriptions().clear();
|
|
115
138
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { registerEvent, listEvents, getEvent } from "./registry.js";
|
|
2
|
-
export { emit, emitAsync, subscribe, unsubscribe, listSubscriptions, } from "./bus.js";
|
|
2
|
+
export { emit, emitAsync, subscribe, subscribeAll, unsubscribe, listSubscriptions, } from "./bus.js";
|
|
3
3
|
export type { EventDefinition, EventSubscription, EventMeta } from "./types.js";
|
package/dist/event-bus/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { registerEvent, listEvents, getEvent } from "./registry.js";
|
|
2
|
-
export { emit, emitAsync, subscribe, unsubscribe, listSubscriptions, } from "./bus.js";
|
|
2
|
+
export { emit, emitAsync, subscribe, subscribeAll, unsubscribe, listSubscriptions, } from "./bus.js";
|
|
@@ -1,6 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the iframe CSP from the img-src / media-src lists.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: both lists are validated again here, at the point they are
|
|
5
|
+
* interpolated, rather than trusted because they came from app config. The
|
|
6
|
+
* resolved config is a cached, mutable object, and the client renderers pass
|
|
7
|
+
* lists fetched from the server, so neither is guaranteed to still be what the
|
|
8
|
+
* schema accepted. A list that fails validation throws instead of producing a
|
|
9
|
+
* policy with an injected directive.
|
|
10
|
+
*/
|
|
11
|
+
export declare function extensionIframeCspBase(imageSources: readonly string[], mediaSources: readonly string[]): string;
|
|
1
12
|
export declare const EXTENSION_FRAME_ANCESTORS: string;
|
|
2
13
|
export declare const EXTENSION_IFRAME_CSP: string;
|
|
3
|
-
export declare const EXTENSION_IFRAME_META_CSP
|
|
14
|
+
export declare const EXTENSION_IFRAME_META_CSP: string;
|
|
15
|
+
/**
|
|
16
|
+
* Authenticated endpoint that returns the deployment's validated
|
|
17
|
+
* `{ imageSources, mediaSources }`, so the client-rendered `srcDoc` frames
|
|
18
|
+
* (`ExtensionViewer`, `InlineExtensionFrame`) can apply the same img-src /
|
|
19
|
+
* media-src policy as the server render route without importing app config.
|
|
20
|
+
*/
|
|
21
|
+
export declare const EXTENSION_IFRAME_DISPLAY_SOURCES_PATH = "/_agent-native/extensions/iframe/display-sources";
|
|
22
|
+
/**
|
|
23
|
+
* Builds the meta CSP for a client-rendered frame from the
|
|
24
|
+
* `EXTENSION_IFRAME_DISPLAY_SOURCES_PATH` response. Anything that is not a
|
|
25
|
+
* valid pair of source lists falls back to the default policy, which blocks
|
|
26
|
+
* every remote image and media source (fail closed).
|
|
27
|
+
*/
|
|
28
|
+
export declare function extensionIframeMetaCspFromDisplaySources(value: unknown): string;
|
|
4
29
|
/**
|
|
5
30
|
* SECURITY — EXTENSION CONTENT IS UNTRUSTED.
|
|
6
31
|
*
|
|
@@ -21,6 +46,14 @@ export declare const EXTENSION_IFRAME_META_CSP = "default-src 'none'; script-src
|
|
|
21
46
|
* header sanitization, and method allowlist; do not relax those gates
|
|
22
47
|
* for "convenience" in this file or any caller.
|
|
23
48
|
*
|
|
49
|
+
* 3. `connect-src` stays `'self'` and stays out of app config. Remote
|
|
50
|
+
* images and media are `extensions.iframeImageSources` /
|
|
51
|
+
* `iframeMediaSources`, which reach only `img-src` / `media-src`.
|
|
52
|
+
* Each allowed remote origin is an explicit egress permission: the
|
|
53
|
+
* browser sends the URL, and extension script can encode data into
|
|
54
|
+
* it. API calls stay on the permission-gated host bridge. Token
|
|
55
|
+
* validation still stops a value from appending another directive.
|
|
56
|
+
*
|
|
24
57
|
* For the trust model rationale, see audit 05-tools-sandbox.md (C1) and the
|
|
25
58
|
* `extensions` skill. When in doubt, fail closed.
|
|
26
59
|
*
|
|
@@ -58,4 +91,4 @@ export interface ExtensionRenderBinding {
|
|
|
58
91
|
externalFetch?: boolean;
|
|
59
92
|
};
|
|
60
93
|
}
|
|
61
|
-
export declare function buildExtensionHtml(content: string, themeVars: string, isDark: boolean, extensionId?: string, binding?: ExtensionRenderBinding): string;
|
|
94
|
+
export declare function buildExtensionHtml(content: string, themeVars: string, isDark: boolean, extensionId?: string, binding?: ExtensionRenderBinding, metaCsp?: string): string;
|
|
@@ -1,5 +1,31 @@
|
|
|
1
|
+
// Import-free on purpose: this shell is also built in the browser
|
|
2
|
+
// (ExtensionViewer, InlineExtensionFrame), so it must not import the
|
|
3
|
+
// app-config store. Configured sources are resolved server-side in
|
|
4
|
+
// ./iframe-csp.ts: the render route passes them in, and the client renderers
|
|
5
|
+
// fetch them from EXTENSION_IFRAME_DISPLAY_SOURCES_PATH.
|
|
6
|
+
import { DEFAULT_EXTENSION_DISPLAY_SOURCES, extensionDisplaySourcesError, } from "../app-config/extension-display-sources.js";
|
|
1
7
|
import { buildSessionReplayIframeBootstrap } from "./session-replay-iframe.js";
|
|
2
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Builds the iframe CSP from the img-src / media-src lists.
|
|
10
|
+
*
|
|
11
|
+
* SECURITY: both lists are validated again here, at the point they are
|
|
12
|
+
* interpolated, rather than trusted because they came from app config. The
|
|
13
|
+
* resolved config is a cached, mutable object, and the client renderers pass
|
|
14
|
+
* lists fetched from the server, so neither is guaranteed to still be what the
|
|
15
|
+
* schema accepted. A list that fails validation throws instead of producing a
|
|
16
|
+
* policy with an injected directive.
|
|
17
|
+
*/
|
|
18
|
+
export function extensionIframeCspBase(imageSources, mediaSources) {
|
|
19
|
+
const imageError = extensionDisplaySourcesError(imageSources);
|
|
20
|
+
if (imageError) {
|
|
21
|
+
throw new TypeError(`Invalid extension iframe img-src: ${imageError}`);
|
|
22
|
+
}
|
|
23
|
+
const mediaError = extensionDisplaySourcesError(mediaSources);
|
|
24
|
+
if (mediaError) {
|
|
25
|
+
throw new TypeError(`Invalid extension iframe media-src: ${mediaError}`);
|
|
26
|
+
}
|
|
27
|
+
return `default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src ${imageSources.join(" ")}; media-src ${mediaSources.join(" ")}; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';`;
|
|
28
|
+
}
|
|
3
29
|
export const EXTENSION_FRAME_ANCESTORS = [
|
|
4
30
|
"'self'",
|
|
5
31
|
"https://agent-native.com",
|
|
@@ -10,9 +36,40 @@ export const EXTENSION_FRAME_ANCESTORS = [
|
|
|
10
36
|
"https://*.claudemcpcontent.com",
|
|
11
37
|
"https://*.web-sandbox.oaiusercontent.com",
|
|
12
38
|
].join(" ");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
39
|
+
// The two constants below are the policy an app gets before it configures
|
|
40
|
+
// anything. `buildExtensionIframeCsp()` (server-only, ./iframe-csp.ts) resolves `extensions.iframeImageSources`
|
|
41
|
+
// and `extensions.iframeMediaSources` and is what the render route sets, so a
|
|
42
|
+
// deployment that widens img-src / media-src still gets the same
|
|
43
|
+
// `connect-src 'self'` as one that does not. A remote image or media
|
|
44
|
+
// origin is its own egress permission: the browser requests that URL.
|
|
45
|
+
export const EXTENSION_IFRAME_CSP = `${extensionIframeCspBase(DEFAULT_EXTENSION_DISPLAY_SOURCES, DEFAULT_EXTENSION_DISPLAY_SOURCES)} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;
|
|
46
|
+
export const EXTENSION_IFRAME_META_CSP = extensionIframeCspBase(DEFAULT_EXTENSION_DISPLAY_SOURCES, DEFAULT_EXTENSION_DISPLAY_SOURCES);
|
|
47
|
+
/**
|
|
48
|
+
* Authenticated endpoint that returns the deployment's validated
|
|
49
|
+
* `{ imageSources, mediaSources }`, so the client-rendered `srcDoc` frames
|
|
50
|
+
* (`ExtensionViewer`, `InlineExtensionFrame`) can apply the same img-src /
|
|
51
|
+
* media-src policy as the server render route without importing app config.
|
|
52
|
+
*/
|
|
53
|
+
export const EXTENSION_IFRAME_DISPLAY_SOURCES_PATH = "/_agent-native/extensions/iframe/display-sources";
|
|
54
|
+
/**
|
|
55
|
+
* Builds the meta CSP for a client-rendered frame from the
|
|
56
|
+
* `EXTENSION_IFRAME_DISPLAY_SOURCES_PATH` response. Anything that is not a
|
|
57
|
+
* valid pair of source lists falls back to the default policy, which blocks
|
|
58
|
+
* every remote image and media source (fail closed).
|
|
59
|
+
*/
|
|
60
|
+
export function extensionIframeMetaCspFromDisplaySources(value) {
|
|
61
|
+
if (typeof value !== "object" || value === null) {
|
|
62
|
+
return EXTENSION_IFRAME_META_CSP;
|
|
63
|
+
}
|
|
64
|
+
const { imageSources, mediaSources } = value;
|
|
65
|
+
try {
|
|
66
|
+
return extensionIframeCspBase(imageSources, mediaSources);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return EXTENSION_IFRAME_META_CSP;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function buildExtensionHtml(content, themeVars, isDark, extensionId, binding, metaCsp = EXTENSION_IFRAME_META_CSP) {
|
|
16
73
|
const extensionIdJson = JSON.stringify(extensionId ?? "");
|
|
17
74
|
const extensionIdAttr = escapeHtmlAttribute(extensionId ?? "");
|
|
18
75
|
const bindingJson = JSON.stringify(binding ?? {
|
|
@@ -26,7 +83,7 @@ export function buildExtensionHtml(content, themeVars, isDark, extensionId, bind
|
|
|
26
83
|
<head>
|
|
27
84
|
<meta charset="utf-8" />
|
|
28
85
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
29
|
-
<meta http-equiv="Content-Security-Policy" content="${
|
|
86
|
+
<meta http-equiv="Content-Security-Policy" content="${metaCsp}" />
|
|
30
87
|
${binding && !binding.isAuthor ? `<meta name="agent-native-extension-author" content="${escapeHtmlAttribute(binding.authorEmail)}" />` : ""}
|
|
31
88
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
32
89
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-only: resolves `extensions.iframeImageSources` /
|
|
3
|
+
* `extensions.iframeMediaSources` from app config. Kept out of
|
|
4
|
+
* `html-shell.ts` because that shell is also built in the browser, where the
|
|
5
|
+
* app-config store must not be imported.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildExtensionIframeMetaCsp(): string;
|
|
8
|
+
export interface ExtensionIframeDisplaySources {
|
|
9
|
+
imageSources: string[];
|
|
10
|
+
mediaSources: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The configured img-src / media-src lists for the client-rendered frames,
|
|
14
|
+
* served at `EXTENSION_IFRAME_DISPLAY_SOURCES_PATH`. Validated through the same
|
|
15
|
+
* CSP builder before they leave the server, and copied so the response cannot
|
|
16
|
+
* alias the cached resolved config.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getExtensionIframeDisplaySources(): ExtensionIframeDisplaySources;
|
|
19
|
+
/** The header CSP the render route sets: the meta CSP plus `frame-ancestors`. */
|
|
20
|
+
export declare function buildExtensionIframeCsp(): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
2
|
+
import { EXTENSION_FRAME_ANCESTORS, extensionIframeCspBase, } from "./html-shell.js";
|
|
3
|
+
/**
|
|
4
|
+
* Server-only: resolves `extensions.iframeImageSources` /
|
|
5
|
+
* `extensions.iframeMediaSources` from app config. Kept out of
|
|
6
|
+
* `html-shell.ts` because that shell is also built in the browser, where the
|
|
7
|
+
* app-config store must not be imported.
|
|
8
|
+
*/
|
|
9
|
+
export function buildExtensionIframeMetaCsp() {
|
|
10
|
+
const { iframeImageSources, iframeMediaSources } = getAppConfig().extensions;
|
|
11
|
+
return extensionIframeCspBase(iframeImageSources, iframeMediaSources);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The configured img-src / media-src lists for the client-rendered frames,
|
|
15
|
+
* served at `EXTENSION_IFRAME_DISPLAY_SOURCES_PATH`. Validated through the same
|
|
16
|
+
* CSP builder before they leave the server, and copied so the response cannot
|
|
17
|
+
* alias the cached resolved config.
|
|
18
|
+
*/
|
|
19
|
+
export function getExtensionIframeDisplaySources() {
|
|
20
|
+
const { iframeImageSources, iframeMediaSources } = getAppConfig().extensions;
|
|
21
|
+
extensionIframeCspBase(iframeImageSources, iframeMediaSources);
|
|
22
|
+
return {
|
|
23
|
+
imageSources: [...iframeImageSources],
|
|
24
|
+
mediaSources: [...iframeMediaSources],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** The header CSP the render route sets: the meta CSP plus `frame-ancestors`. */
|
|
28
|
+
export function buildExtensionIframeCsp() {
|
|
29
|
+
return `${buildExtensionIframeMetaCsp()} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;
|
|
30
|
+
}
|
|
@@ -9,7 +9,8 @@ import { runWithRequestContext, getRequestOrgId, } from "../server/request-conte
|
|
|
9
9
|
import { ForbiddenError, resolveAccess } from "../sharing/access.js";
|
|
10
10
|
import { ROLE_RANK } from "../sharing/schema.js";
|
|
11
11
|
import { ExtensionContentEditError } from "./content-patch.js";
|
|
12
|
-
import { buildExtensionHtml
|
|
12
|
+
import { buildExtensionHtml } from "./html-shell.js";
|
|
13
|
+
import { buildExtensionIframeCsp, buildExtensionIframeMetaCsp, getExtensionIframeDisplaySources, } from "./iframe-csp.js";
|
|
13
14
|
import { getLocalExtension, isLocalExtensionRow, listLocalExtensions, } from "./local.js";
|
|
14
15
|
import { collectSecretValues, normalizeExtensionProxyMethod, readResponseTextWithLimit, redactSecrets, redactString, sanitizeOutboundHeaders, } from "./proxy-security.js";
|
|
15
16
|
import { listExtensions, getExtension, getExtensionHistoryVersion, listExtensionHistory, createExtension, updateExtension, updateExtensionContent, restoreExtensionHistoryVersion, deleteExtension, hideExtension, unhideExtension, globalHideExtension, globalUnhideExtension, ensureExtensionsTables, } from "./store.js";
|
|
@@ -51,6 +52,17 @@ export function createExtensionsHandler(options = {}) {
|
|
|
51
52
|
setResponseStatus(event, 403);
|
|
52
53
|
return { error: "Extension creation is disabled for this app" };
|
|
53
54
|
}
|
|
55
|
+
// The configured img-src / media-src for client-rendered srcDoc frames
|
|
56
|
+
// (EXTENSION_IFRAME_DISPLAY_SOURCES_PATH). It only reads validated app
|
|
57
|
+
// config, so it is answered before the extension tables are set up.
|
|
58
|
+
// Two segments, and no extension route uses `display-sources`, so this
|
|
59
|
+
// cannot shadow an extension id.
|
|
60
|
+
if (method === "GET" &&
|
|
61
|
+
parts.length === 2 &&
|
|
62
|
+
parts[0] === "iframe" &&
|
|
63
|
+
parts[1] === "display-sources") {
|
|
64
|
+
return getExtensionIframeDisplaySources();
|
|
65
|
+
}
|
|
54
66
|
await ensureExtensionsTables();
|
|
55
67
|
return dispatch(event, method, parts, userEmail);
|
|
56
68
|
});
|
|
@@ -130,9 +142,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
130
142
|
role: "viewer",
|
|
131
143
|
source: "local-files",
|
|
132
144
|
permissions: localExtension.source.permissions,
|
|
133
|
-
});
|
|
145
|
+
}, buildExtensionIframeMetaCsp());
|
|
134
146
|
setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
135
|
-
setResponseHeader(event, "Content-Security-Policy",
|
|
147
|
+
setResponseHeader(event, "Content-Security-Policy", buildExtensionIframeCsp());
|
|
136
148
|
setResponseHeader(event, "X-Content-Type-Options", "nosniff");
|
|
137
149
|
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
138
150
|
return html;
|
|
@@ -153,11 +165,11 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
153
165
|
viewerEmail: userEmail,
|
|
154
166
|
isAuthor,
|
|
155
167
|
role: renderRole,
|
|
156
|
-
});
|
|
168
|
+
}, buildExtensionIframeMetaCsp());
|
|
157
169
|
// Security headers per render. `frame-ancestors` in the CSP must be set as
|
|
158
170
|
// an HTTP header to be enforced; meta-CSP can't set it per spec.
|
|
159
171
|
setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
160
|
-
setResponseHeader(event, "Content-Security-Policy",
|
|
172
|
+
setResponseHeader(event, "Content-Security-Policy", buildExtensionIframeCsp());
|
|
161
173
|
setResponseHeader(event, "X-Content-Type-Options", "nosniff");
|
|
162
174
|
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
163
175
|
return html;
|
|
@@ -34,6 +34,8 @@ export interface MCPConfig {
|
|
|
34
34
|
productionActions?: Record<string, ActionEntry>;
|
|
35
35
|
/** Unlisted from this MCP surface; used only when minting scoped widget tickets. */
|
|
36
36
|
widgetReadActions?: Record<string, ActionEntry>;
|
|
37
|
+
/** Unlisted from this MCP surface; editor mutations callable only by scoped widget grants. */
|
|
38
|
+
widgetWriteActions?: Record<string, ActionEntry>;
|
|
37
39
|
askAgent?: (message: string) => Promise<string>;
|
|
38
40
|
builtinCrossAppTools?: boolean;
|
|
39
41
|
/**
|
|
@@ -55,7 +57,15 @@ export interface MCPConfig {
|
|
|
55
57
|
widgets?: boolean;
|
|
56
58
|
widgetDomain?: string;
|
|
57
59
|
widgetTargets?: Record<string, (args: Record<string, unknown>, result: unknown) => McpDirectoryWidgetTarget | null>;
|
|
60
|
+
authorizeWidgetWrite?: (input: {
|
|
61
|
+
toolName: string;
|
|
62
|
+
args: Record<string, unknown>;
|
|
63
|
+
result: unknown;
|
|
64
|
+
target: McpDirectoryWidgetTarget;
|
|
65
|
+
identity: MCPCallerIdentity;
|
|
66
|
+
}) => boolean | Promise<boolean>;
|
|
58
67
|
widgetReadActionArguments?: Record<string, Record<string, McpDirectoryWidgetReadArgument>>;
|
|
68
|
+
widgetWriteActionArguments?: Record<string, Record<string, McpDirectoryWidgetReadArgument>>;
|
|
59
69
|
/** Actions whose capability-backed `mcp-widget` execution is strictly read-only. */
|
|
60
70
|
widgetReadOnlyActions?: readonly string[];
|
|
61
71
|
/** Unlisted public reads that are available only through a scoped widget ticket. */
|
|
@@ -79,10 +89,14 @@ export interface MCPConfig {
|
|
|
79
89
|
export interface McpDirectoryWidgetTarget {
|
|
80
90
|
targetPath: string;
|
|
81
91
|
resourceIds: Record<string, string>;
|
|
92
|
+
/** Mutations available to this artifact's editor, selected from the profile allowlist. */
|
|
93
|
+
writeActions?: readonly string[];
|
|
82
94
|
}
|
|
83
95
|
export interface MCPCallerIdentity {
|
|
84
96
|
userEmail: string | undefined;
|
|
85
97
|
identityAssurance?: "user" | "organization" | "service";
|
|
98
|
+
/** Issue time, in milliseconds, from a verified app-issued MCP credential. */
|
|
99
|
+
mcpCredentialIssuedAtMs?: number;
|
|
86
100
|
orgId?: string | null;
|
|
87
101
|
orgDomain: string | undefined;
|
|
88
102
|
oauthScopes?: string[];
|
|
@@ -108,6 +122,7 @@ export declare class McpDirectoryProfileValidationError extends Error {
|
|
|
108
122
|
}
|
|
109
123
|
export declare function selectMcpActionSurface(config: MCPConfig, requestMeta?: MCPRequestMeta): Record<string, ActionEntry>;
|
|
110
124
|
export declare function selectMcpDirectoryWidgetReadActions(profile: MCPConfig["directoryProfile"] | undefined, actions: Record<string, ActionEntry>): Record<string, ActionEntry> | undefined;
|
|
125
|
+
export declare function selectMcpDirectoryWidgetWriteActions(profile: MCPConfig["directoryProfile"] | undefined, actions: Record<string, ActionEntry>): Record<string, ActionEntry> | undefined;
|
|
111
126
|
export declare function getConfiguredMcpOwnerEmail(): string | undefined;
|
|
112
127
|
export declare function validateMcpDirectoryProfile(config: MCPConfig, sourceActions?: Record<string, ActionEntry>): void;
|
|
113
128
|
export declare function validateMcpDirectoryWidgetDomain(domain: string | undefined): void;
|