veryfront 0.1.229 → 0.1.230

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 (39) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/agent/hosted-child-lifecycle.d.ts +41 -0
  3. package/esm/src/agent/hosted-child-lifecycle.d.ts.map +1 -0
  4. package/esm/src/agent/hosted-child-lifecycle.js +47 -0
  5. package/esm/src/agent/index.d.ts +1 -0
  6. package/esm/src/agent/index.d.ts.map +1 -1
  7. package/esm/src/agent/index.js +1 -0
  8. package/esm/src/channels/control-plane.d.ts +21 -0
  9. package/esm/src/channels/control-plane.d.ts.map +1 -1
  10. package/esm/src/channels/control-plane.js +48 -0
  11. package/esm/src/channels/invoke.d.ts +1 -1
  12. package/esm/src/channels/invoke.d.ts.map +1 -1
  13. package/esm/src/channels/invoke.js +1 -1
  14. package/esm/src/server/handlers/preview/hmr.handler.d.ts.map +1 -1
  15. package/esm/src/server/handlers/preview/hmr.handler.js +21 -9
  16. package/esm/src/server/runtime-handler/adapter-factory.d.ts +5 -2
  17. package/esm/src/server/runtime-handler/adapter-factory.d.ts.map +1 -1
  18. package/esm/src/server/runtime-handler/adapter-factory.js +18 -1
  19. package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
  20. package/esm/src/server/runtime-handler/index.js +5 -2
  21. package/esm/src/server/services/rsc/orchestrators/page-handler.d.ts.map +1 -1
  22. package/esm/src/server/services/rsc/orchestrators/page-handler.js +22 -1
  23. package/esm/src/server/utils/proxy-trust.d.ts +33 -0
  24. package/esm/src/server/utils/proxy-trust.d.ts.map +1 -0
  25. package/esm/src/server/utils/proxy-trust.js +41 -0
  26. package/esm/src/utils/version-constant.d.ts +1 -1
  27. package/esm/src/utils/version-constant.js +1 -1
  28. package/package.json +1 -1
  29. package/src/deno.js +1 -1
  30. package/src/src/agent/hosted-child-lifecycle.ts +121 -0
  31. package/src/src/agent/index.ts +7 -0
  32. package/src/src/channels/control-plane.ts +52 -0
  33. package/src/src/channels/invoke.ts +1 -1
  34. package/src/src/server/handlers/preview/hmr.handler.ts +32 -26
  35. package/src/src/server/runtime-handler/adapter-factory.ts +23 -3
  36. package/src/src/server/runtime-handler/index.ts +5 -2
  37. package/src/src/server/services/rsc/orchestrators/page-handler.ts +23 -1
  38. package/src/src/server/utils/proxy-trust.ts +56 -0
  39. package/src/src/utils/version-constant.ts +1 -1
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.229",
3
+ "version": "0.1.230",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -0,0 +1,41 @@
1
+ export interface HostedChildLifecycleTerminalState {
2
+ status: "completed" | "failed" | "cancelled";
3
+ usage?: {
4
+ inputTokens?: number;
5
+ outputTokens?: number;
6
+ totalTokens?: number;
7
+ };
8
+ terminalErrorCode?: string | null;
9
+ terminalErrorMessage?: string | null;
10
+ }
11
+ export interface HostedChildLifecycleCompletedState extends Omit<HostedChildLifecycleTerminalState, "status"> {
12
+ status: "completed";
13
+ }
14
+ export interface HostedChildLifecycleAdapter {
15
+ pending?: () => Promise<void> | void;
16
+ running?: () => Promise<void> | void;
17
+ completed?: (terminalState: HostedChildLifecycleTerminalState) => Promise<void> | void;
18
+ failed?: (terminalState: HostedChildLifecycleTerminalState) => Promise<void> | void;
19
+ cancelled?: (terminalState: HostedChildLifecycleTerminalState) => Promise<void> | void;
20
+ }
21
+ export interface HostedChildLifecycleErrorState extends Omit<HostedChildLifecycleTerminalState, "status"> {
22
+ status: "failed" | "cancelled";
23
+ }
24
+ export interface HostedChildLifecycleRunnerOptions<TResult> {
25
+ adapter: HostedChildLifecycleAdapter;
26
+ execute: () => Promise<TResult> | TResult;
27
+ resolveCompletedState?: (result: TResult) => Promise<HostedChildLifecycleCompletedState> | HostedChildLifecycleCompletedState;
28
+ resolveErrorState: (error: unknown) => Promise<HostedChildLifecycleErrorState> | HostedChildLifecycleErrorState;
29
+ onLifecycleError?: (error: unknown) => Promise<void> | void;
30
+ }
31
+ export type HostedChildLifecycleRunResult<TResult> = {
32
+ status: "completed";
33
+ result: TResult;
34
+ terminalState: HostedChildLifecycleTerminalState;
35
+ } | {
36
+ status: "failed" | "cancelled";
37
+ error: unknown;
38
+ terminalState: HostedChildLifecycleTerminalState;
39
+ };
40
+ export declare function runHostedChildLifecycle<TResult>(options: HostedChildLifecycleRunnerOptions<TResult>): Promise<HostedChildLifecycleRunResult<TResult>>;
41
+ //# sourceMappingURL=hosted-child-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hosted-child-lifecycle.d.ts","sourceRoot":"","sources":["../../../src/src/agent/hosted-child-lifecycle.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iCAAiC;IAChD,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC7C,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,kCACf,SAAQ,IAAI,CAAC,iCAAiC,EAAE,QAAQ,CAAC;IACzD,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,CACV,aAAa,EAAE,iCAAiC,KAC7C,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,CACP,aAAa,EAAE,iCAAiC,KAC7C,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,CACV,aAAa,EAAE,iCAAiC,KAC7C,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,8BACf,SAAQ,IAAI,CAAC,iCAAiC,EAAE,QAAQ,CAAC;IACzD,MAAM,EAAE,QAAQ,GAAG,WAAW,CAAC;CAChC;AAED,MAAM,WAAW,iCAAiC,CAAC,OAAO;IACxD,OAAO,EAAE,2BAA2B,CAAC;IACrC,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC1C,qBAAqB,CAAC,EAAE,CACtB,MAAM,EAAE,OAAO,KAEb,OAAO,CAAC,kCAAkC,CAAC,GAC3C,kCAAkC,CAAC;IACvC,iBAAiB,EAAE,CACjB,KAAK,EAAE,OAAO,KAEZ,OAAO,CAAC,8BAA8B,CAAC,GACvC,8BAA8B,CAAC;IACnC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7D;AAED,MAAM,MAAM,6BAA6B,CAAC,OAAO,IAC7C;IACA,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,iCAAiC,CAAC;CAClD,GACC;IACA,MAAM,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,iCAAiC,CAAC;CAClD,CAAC;AAmBJ,wBAAsB,uBAAuB,CAAC,OAAO,EACnD,OAAO,EAAE,iCAAiC,CAAC,OAAO,CAAC,GAClD,OAAO,CAAC,6BAA6B,CAAC,OAAO,CAAC,CAAC,CAsCjD"}
@@ -0,0 +1,47 @@
1
+ async function dispatchTerminalState(adapter, terminalState) {
2
+ if (terminalState.status === "cancelled") {
3
+ await adapter.cancelled?.(terminalState);
4
+ return;
5
+ }
6
+ if (terminalState.status === "failed") {
7
+ await adapter.failed?.(terminalState);
8
+ return;
9
+ }
10
+ await adapter.completed?.(terminalState);
11
+ }
12
+ export async function runHostedChildLifecycle(options) {
13
+ await options.adapter.pending?.();
14
+ await options.adapter.running?.();
15
+ let result;
16
+ try {
17
+ result = await options.execute();
18
+ }
19
+ catch (error) {
20
+ const terminalState = await options.resolveErrorState(error);
21
+ try {
22
+ await dispatchTerminalState(options.adapter, terminalState);
23
+ }
24
+ catch (lifecycleError) {
25
+ if (options.onLifecycleError) {
26
+ await options.onLifecycleError(lifecycleError);
27
+ }
28
+ else {
29
+ throw lifecycleError;
30
+ }
31
+ }
32
+ return {
33
+ status: terminalState.status,
34
+ error,
35
+ terminalState,
36
+ };
37
+ }
38
+ const terminalState = options.resolveCompletedState
39
+ ? await options.resolveCompletedState(result)
40
+ : { status: "completed" };
41
+ await dispatchTerminalState(options.adapter, terminalState);
42
+ return {
43
+ status: "completed",
44
+ result,
45
+ terminalState,
46
+ };
47
+ }
@@ -89,6 +89,7 @@ export { type AgUiRuntimeContextItem, AgUiRuntimeContextItemSchema, type AgUiRun
89
89
  export { normalizeAgUiRuntimeMessages } from "./ag-ui-runtime-support.js";
90
90
  export { type AgUiBrowserEncodedEvent, type AgUiBrowserEncoderState, type AgUiBrowserRunFinishedMetadata, type AgUiRuntimeStreamEvent, buildAgUiBrowserFinalizeResponse, createAgUiBrowserEncoderState, finalizeAgUiBrowserEvents, mapRuntimeStreamEventToAgUiBrowserEvents, } from "./ag-ui-browser-encoder.js";
91
91
  export { type AgUiBrowserResponseEncoder, type AgUiBrowserResponseExecution, type AgUiBrowserResponseRequestState, createAgUiBrowserResponseStream, type CreateAgUiBrowserResponseStreamInput, } from "./ag-ui-browser-response-stream.js";
92
+ export { type HostedChildLifecycleAdapter, type HostedChildLifecycleRunnerOptions, type HostedChildLifecycleRunResult, type HostedChildLifecycleTerminalState, runHostedChildLifecycle, } from "./hosted-child-lifecycle.js";
92
93
  export { type HostedLifecycleAdapter, type HostedLifecycleExecution, type HostedLifecycleRunnerOptions, type HostedLifecycleRunResult, type HostedLifecycleTerminalState, runHostedLifecycle, } from "./hosted-lifecycle.js";
93
94
  export { mergeToolCallInput, mergeToolInputDelta, parseDataStreamSseEvents, parseToolInputObject, streamDataStreamEvents, stripLeadingEmptyObjectPlaceholder, } from "./data-stream.js";
94
95
  export { expandAllowedRemoteToolNames, getProviderNativeToolNames, type ProviderNativeToolInventoryOptions, } from "./provider-native-tool-inventory.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AACH,OAAO,yBAAyB,CAAC;AAGjC,YAAY,EACV,KAAK,EACL,WAAW,EACX,YAAY,EACZ,eAAe,EACf,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,YAAY,EACZ,OAAO,IAAI,YAAY,EACvB,WAAW,EACX,aAAa,EACb,WAAW,EACX,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEnF,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,WAAW,EACX,KAAK,iBAAiB,EACtB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,WAAW,EACX,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,iCAAiC,EACtC,KAAK,yBAAyB,EAC9B,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,KAAK,uBAAuB,EAC5B,6BAA6B,EAC7B,KAAK,kBAAkB,EACvB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,wBAAwB,EACxB,uBAAuB,EACvB,8BAA8B,GAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,gCAAgC,EAChC,6BAA6B,EAC7B,yBAAyB,EACzB,wCAAwC,GACzC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACpC,+BAA+B,EAC/B,KAAK,oCAAoC,GAC1C,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,kCAAkC,GACnC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,KAAK,kCAAkC,GACxC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAC7B,8BAA8B,EAC9B,8BAA8B,EAC9B,wBAAwB,EACxB,KAAK,6BAA6B,GACnC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,YAAY,EACjB,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,EACrB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,qBAAqB,EACrB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,8BAA8B,EAC9B,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,4BAA4B,EAC5B,iBAAiB,EACjB,KAAK,wBAAwB,GAC9B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,KAAK,8BAA8B,EACnC,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/agent/index.ts"],"names":[],"mappings":"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"}
@@ -88,6 +88,7 @@ export { AgUiRuntimeContextItemSchema, AgUiRuntimeInjectedToolSchema, AgUiRuntim
88
88
  export { normalizeAgUiRuntimeMessages } from "./ag-ui-runtime-support.js";
89
89
  export { buildAgUiBrowserFinalizeResponse, createAgUiBrowserEncoderState, finalizeAgUiBrowserEvents, mapRuntimeStreamEventToAgUiBrowserEvents, } from "./ag-ui-browser-encoder.js";
90
90
  export { createAgUiBrowserResponseStream, } from "./ag-ui-browser-response-stream.js";
91
+ export { runHostedChildLifecycle, } from "./hosted-child-lifecycle.js";
91
92
  export { runHostedLifecycle, } from "./hosted-lifecycle.js";
92
93
  export { mergeToolCallInput, mergeToolInputDelta, parseDataStreamSseEvents, parseToolInputObject, streamDataStreamEvents, stripLeadingEmptyObjectPlaceholder, } from "./data-stream.js";
93
94
  export { expandAllowedRemoteToolNames, getProviderNativeToolNames, } from "./provider-native-tool-inventory.js";
@@ -159,6 +159,27 @@ export interface RuntimeAgentDiscoveryDeps {
159
159
  getAllAgentIds: () => string[];
160
160
  }
161
161
  export declare function listRuntimeAgents(ctx: HandlerContext, deps: RuntimeAgentDiscoveryDeps): Promise<RuntimeAgentListResponse>;
162
+ /**
163
+ * Verify the Ed25519 signature of a dispatch JWS and the recency of its
164
+ * timestamps, without binding to a particular request body or audience.
165
+ *
166
+ * This is intentionally weaker than {@link verifyDispatchJws}: it answers
167
+ * "was this JWS minted by a holder of the control-plane private key and is it
168
+ * still fresh?" and is used as a trust signal in code paths (proxy-trust,
169
+ * adapter selection) that don't yet have access to the authoritative request
170
+ * body or project audience. Callers that consume request payloads MUST still
171
+ * call {@link verifyDispatchJws} / {@link verifyControlPlaneJws} to bind the
172
+ * signature to the body and project.
173
+ *
174
+ * Returns true iff the signature verifies and `iat`/`exp` are within the
175
+ * allowed skew and max-age window. All other failures (including parsing
176
+ * errors) resolve to false so callers can treat the signal as present-but-not-
177
+ * proven without raising.
178
+ */
179
+ export declare function verifyDispatchJwsSignature(jws: string, options: {
180
+ publicKeyPem: string;
181
+ maxAgeSeconds: number;
182
+ }): Promise<boolean>;
162
183
  export declare function verifyDispatchJws(jws: string, body: string, options: {
163
184
  audience: string;
164
185
  expectedPlatform?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../../src/src/channels/control-plane.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../../src/src/channels/control-plane.ts"],"names":[],"mappings":"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"}
@@ -197,6 +197,54 @@ export async function listRuntimeAgents(ctx, deps) {
197
197
  .sort((left, right) => left.name.localeCompare(right.name));
198
198
  return RuntimeAgentListResponseSchema.parse({ agents });
199
199
  }
200
+ /**
201
+ * Verify the Ed25519 signature of a dispatch JWS and the recency of its
202
+ * timestamps, without binding to a particular request body or audience.
203
+ *
204
+ * This is intentionally weaker than {@link verifyDispatchJws}: it answers
205
+ * "was this JWS minted by a holder of the control-plane private key and is it
206
+ * still fresh?" and is used as a trust signal in code paths (proxy-trust,
207
+ * adapter selection) that don't yet have access to the authoritative request
208
+ * body or project audience. Callers that consume request payloads MUST still
209
+ * call {@link verifyDispatchJws} / {@link verifyControlPlaneJws} to bind the
210
+ * signature to the body and project.
211
+ *
212
+ * Returns true iff the signature verifies and `iat`/`exp` are within the
213
+ * allowed skew and max-age window. All other failures (including parsing
214
+ * errors) resolve to false so callers can treat the signal as present-but-not-
215
+ * proven without raising.
216
+ */
217
+ export async function verifyDispatchJwsSignature(jws, options) {
218
+ try {
219
+ const parts = jws.split(".");
220
+ if (parts.length !== 3)
221
+ return false;
222
+ const [encodedHeader, encodedPayload, encodedSignature] = parts;
223
+ if (!encodedHeader || !encodedPayload || !encodedSignature)
224
+ return false;
225
+ compactJwsHeaderSchema.parse(parseCompactJwsPart(encodedHeader));
226
+ const claims = dispatchClaimsSchema.parse(parseCompactJwsPart(encodedPayload));
227
+ const signingInput = new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`);
228
+ const signature = base64urlDecodeToBytes(encodedSignature);
229
+ const publicKey = await importEd25519PublicKey(options.publicKeyPem);
230
+ const verified = await dntShim.crypto.subtle.verify("Ed25519", publicKey, signature, signingInput);
231
+ if (!verified)
232
+ return false;
233
+ if (claims.iss !== "veryfront-api")
234
+ return false;
235
+ const now = Math.floor(Date.now() / 1000);
236
+ if (claims.exp <= now)
237
+ return false;
238
+ if (claims.iat > now + SIGNATURE_SKEW_SECONDS)
239
+ return false;
240
+ if (now - claims.iat > options.maxAgeSeconds)
241
+ return false;
242
+ return true;
243
+ }
244
+ catch {
245
+ return false;
246
+ }
247
+ }
200
248
  export async function verifyDispatchJws(jws, body, options) {
201
249
  return verifySignedRequestJws(jws, body, {
202
250
  audience: options.audience,
@@ -139,7 +139,7 @@ export interface ChannelInvokeDeps extends RuntimeAgentDiscoveryDeps {
139
139
  }
140
140
  export declare const defaultChannelInvokeDeps: ChannelInvokeDeps;
141
141
  export declare function listChannelAssistants(ctx: HandlerContext, deps: ChannelInvokeDeps): Promise<ChannelAssistantsResponse>;
142
- export { verifyDispatchJws } from "./control-plane.js";
142
+ export { verifyDispatchJws, verifyDispatchJwsSignature } from "./control-plane.js";
143
143
  export declare function normalizeConversationHistoryForRuntime(messages: ChannelInvokeRequest["conversationHistory"]): Message[];
144
144
  export declare function resolveChannelInvokeAgent(assistantId: string, deps: Pick<ChannelInvokeDeps, "getAgent">): Agent | undefined;
145
145
  export declare function buildChannelResponseParts(response: AgentResponse): ChannelResponsePart[];
@@ -1 +1 @@
1
- {"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../../src/src/channels/invoke.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,IAAI,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAqB,KAAK,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AA4CvF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAiC,CAAC;AAEzE,eAAO,MAAM,8BAA8B;;;;iBAIzC,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;iBAKjC,CAAC;AAEH,eAAO,MAAM,+BAA+B;;;;;;;iBAE1C,CAAC;AAiCH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;2BAMpC,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAYtC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AACtF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AACrE,MAAM,WAAW,iBAAkB,SAAQ,yBAAyB;CAAG;AAEvE,eAAO,MAAM,wBAAwB,EAAE,iBAItC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,yBAAyB,CAAC,CAYpC;AACD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAqCvD,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,oBAAoB,CAAC,qBAAqB,CAAC,GACpD,OAAO,EAAE,CAUX;AAED,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC,GACxC,KAAK,GAAG,SAAS,CAEnB;AAsDD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,aAAa,GAAG,mBAAmB,EAAE,CAiDxF;AAgBD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAkEhC"}
1
+ {"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../../src/src/channels/invoke.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,IAAI,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAqB,KAAK,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AA4CvF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAiC,CAAC;AAEzE,eAAO,MAAM,8BAA8B;;;;iBAIzC,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;iBAKjC,CAAC;AAEH,eAAO,MAAM,+BAA+B;;;;;;;iBAE1C,CAAC;AAiCH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;2BAMpC,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAYtC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AACtF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AACrE,MAAM,WAAW,iBAAkB,SAAQ,yBAAyB;CAAG;AAEvE,eAAO,MAAM,wBAAwB,EAAE,iBAItC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,yBAAyB,CAAC,CAYpC;AACD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAqCnF,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,oBAAoB,CAAC,qBAAqB,CAAC,GACpD,OAAO,EAAE,CAUX;AAED,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC,GACxC,KAAK,GAAG,SAAS,CAEnB;AAsDD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,aAAa,GAAG,mBAAmB,EAAE,CAiDxF;AAgBD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAkEhC"}
@@ -116,7 +116,7 @@ export async function listChannelAssistants(ctx, deps) {
116
116
  }));
117
117
  return ChannelAssistantsResponseSchema.parse({ assistants });
118
118
  }
119
- export { verifyDispatchJws } from "./control-plane.js";
119
+ export { verifyDispatchJws, verifyDispatchJwsSignature } from "./control-plane.js";
120
120
  function normalizeConversationPart(part) {
121
121
  if (part.type === "text" && typeof part.text === "string") {
122
122
  return { type: "text", text: part.text };
@@ -1 +1 @@
1
- {"version":3,"file":"hmr.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/hmr.handler.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAoBrB,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAK7D,qBAAa,UAAW,SAAQ,WAAW;IACzC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAgD;IAC1E,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAA6B;IAC7D,OAAO,CAAC,MAAM,CAAC,4BAA4B,CAAK;IAChD,OAAO,CAAC,MAAM,CAAC,WAAW,CAAS;IAEnC,QAAQ,EAAE,eAAe,CAKvB;IAEF,OAAO,CAAC,MAAM,CAAC,UAAU;IAsCzB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA0JjE;;;;;OAKG;YACW,wBAAwB;IAuCtC,MAAM,CAAC,cAAc,IAAI,MAAM;IAI/B,MAAM,CAAC,UAAU,IAAI;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,iBAAiB,EAAE,MAAM,CAAC;KAC3B;IAID,MAAM,CAAC,+BAA+B,IAAI,MAAM,IAAI;IAWpD,MAAM,CAAC,QAAQ,IAAI,IAAI;IAcvB,OAAO,CAAC,MAAM,CAAC,cAAc;CAO9B"}
1
+ {"version":3,"file":"hmr.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/hmr.handler.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAsBrB,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAK7D,qBAAa,UAAW,SAAQ,WAAW;IACzC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAgD;IAC1E,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAA6B;IAC7D,OAAO,CAAC,MAAM,CAAC,4BAA4B,CAAK;IAChD,OAAO,CAAC,MAAM,CAAC,WAAW,CAAS;IAEnC,QAAQ,EAAE,eAAe,CAKvB;IAEF,OAAO,CAAC,MAAM,CAAC,UAAU;IAsCnB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA8JvE;;;;;OAKG;YACW,wBAAwB;IAuCtC,MAAM,CAAC,cAAc,IAAI,MAAM;IAI/B,MAAM,CAAC,UAAU,IAAI;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,iBAAiB,EAAE,MAAM,CAAC;KAC3B;IAID,MAAM,CAAC,+BAA+B,IAAI,MAAM,IAAI;IAWpD,MAAM,CAAC,QAAQ,IAAI,IAAI;IAcvB,OAAO,CAAC,MAAM,CAAC,cAAc;CAO9B"}
@@ -11,6 +11,8 @@ import { addClient, clearAll, getClient, getClientCount, getClientDetails, remov
11
11
  import { getPingIntervalMs, startPingInterval, stopPingInterval } from "./hmr-ping-keepalive.js";
12
12
  import { broadcastUpdate, getMetrics } from "./hmr-message-router.js";
13
13
  import { getEffectiveRequestHost } from "../../utils/request-host.js";
14
+ import { isProxyTrusted } from "../../utils/proxy-trust.js";
15
+ import { getHostEnv } from "../../../platform/compat/process.js";
14
16
  const logger = serverLogger.component("hmr-handler");
15
17
  // Priority between auth (0) and high (100)
16
18
  const PRIORITY_HMR = HandlerPriority.EARLY;
@@ -56,14 +58,24 @@ export class HMRHandler extends BaseHandler {
56
58
  pingIntervalMs: getPingIntervalMs(),
57
59
  });
58
60
  }
59
- handle(req, ctx) {
61
+ async handle(req, ctx) {
60
62
  if (!this.shouldHandle(req, ctx))
61
- return Promise.resolve(this.continue());
63
+ return this.continue();
62
64
  const url = new URL(req.url);
63
65
  const queryEnv = url.searchParams.get("x-environment");
64
66
  const isPreviewMode = ctx.requestContext?.mode === "preview" || queryEnv === "preview";
65
67
  const isLocal = !!ctx.isLocalProject;
66
- const host = getEffectiveRequestHost(req, url);
68
+ // SECURITY: x-forwarded-host is client-controlled unless we trust the upstream proxy.
69
+ // Honouring it unconditionally lets any remote client present `x-forwarded-host: localhost`
70
+ // and unlock the localhost short-circuit that opens HMR (VULN-SRV-4). Only consult
71
+ // forwarded headers when the request is proxy-trusted; otherwise use Host / url.host.
72
+ // Proxy trust requires a verifiable dispatch JWS (or operator opt-in) — mere header
73
+ // presence is not enough, since `x-veryfront-dispatch-jws` is not stripped on ingress.
74
+ const publicKeyPem = ctx.adapter?.env?.get("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY") ??
75
+ getHostEnv("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY");
76
+ const host = (await isProxyTrusted(req, { publicKeyPem }))
77
+ ? getEffectiveRequestHost(req, url)
78
+ : (req.headers.get("host") ?? url.host);
67
79
  const isLocalhost = isLocalDevHost(host);
68
80
  if (!isPreviewMode && !isLocal && !isLocalhost) {
69
81
  logger.warn("Skipping /_ws - not preview, local dev, or localhost", {
@@ -75,7 +87,7 @@ export class HMRHandler extends BaseHandler {
75
87
  isLocal,
76
88
  isLocalhost,
77
89
  });
78
- return Promise.resolve(this.continue());
90
+ return this.continue();
79
91
  }
80
92
  HMRHandler.initialize();
81
93
  // In proxy mode, ensure the adapter is initialized so WebSocketManager connects
@@ -90,7 +102,7 @@ export class HMRHandler extends BaseHandler {
90
102
  });
91
103
  }
92
104
  if (req.headers.get("upgrade")?.toLowerCase() !== "websocket") {
93
- return Promise.resolve(this.respond(new Response(JSON.stringify({
105
+ return this.respond(new Response(JSON.stringify({
94
106
  status: "ok",
95
107
  clients: getClientCount(),
96
108
  clientDetails: getClientDetails(),
@@ -99,10 +111,10 @@ export class HMRHandler extends BaseHandler {
99
111
  reloadNotifierMetrics: ReloadNotifier.getMetrics(),
100
112
  },
101
113
  message: "HMR WebSocket endpoint - connect via WebSocket",
102
- }), { headers: { "content-type": "application/json" } })));
114
+ }), { headers: { "content-type": "application/json" } }));
103
115
  }
104
116
  if (!ctx.adapter?.server) {
105
- return Promise.resolve(this.respond(new Response("WebSocket not supported", { status: 501 })));
117
+ return this.respond(new Response("WebSocket not supported", { status: 501 }));
106
118
  }
107
119
  try {
108
120
  const { socket, response } = ctx.adapter.server.upgradeWebSocket(req);
@@ -180,11 +192,11 @@ export class HMRHandler extends BaseHandler {
180
192
  projectSlug: ctx.projectSlug,
181
193
  totalClients: getClientCount(),
182
194
  });
183
- return Promise.resolve(this.respond(response));
195
+ return this.respond(response);
184
196
  }
185
197
  catch (error) {
186
198
  logger.error("WebSocket upgrade failed", { error });
187
- return Promise.resolve(this.respond(new Response("WebSocket upgrade failed", { status: 500 })));
199
+ return this.respond(new Response("WebSocket upgrade failed", { status: 500 }));
188
200
  }
189
201
  }
190
202
  /**
@@ -21,6 +21,11 @@ interface AdapterResolutionResult {
21
21
  isLocalProject: boolean;
22
22
  }
23
23
  interface AdapterResolutionOptions {
24
+ /**
25
+ * Inbound request. Used to determine whether forwarded headers such as
26
+ * `x-project-path` can be trusted (see {@link isProxyTrusted}).
27
+ */
28
+ req: Request;
24
29
  /** Base project directory */
25
30
  projectDir: string;
26
31
  /** Base adapter */
@@ -43,8 +48,6 @@ interface AdapterResolutionOptions {
43
48
  environmentName: string | undefined;
44
49
  /** Parsed domain info */
45
50
  parsedDomain: ParsedDomain;
46
- /** Project path from header */
47
- headerProjectPath: string | undefined;
48
51
  /** Whether running in proxy mode */
49
52
  isProxyMode: boolean;
50
53
  /** Optional injectable cache (defaults to module-level singleton) */
@@ -1 +1 @@
1
- {"version":3,"file":"adapter-factory.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/adapter-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAM9D,UAAU,uBAAuB;IAC/B,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,cAAc,CAAC;IACxB,kCAAkC;IAClC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IACpC,yDAAyD;IACzD,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,UAAU,wBAAwB;IAChC,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,6BAA6B;IAC7B,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IACpC,mBAAmB;IACnB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,iBAAiB;IACjB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,kBAAkB;IAClB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,iBAAiB;IACjB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,uCAAuC;IACvC,QAAQ,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;IAC/C,kBAAkB;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,yCAAyC;IACzC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,yBAAyB;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,+BAA+B;IAC/B,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,oCAAoC;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,qEAAqE;IACrE,KAAK,CAAC,EAAE,qBAAqB,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,uBAAuB,CAAC,CAqHlC"}
1
+ {"version":3,"file":"adapter-factory.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/adapter-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAQ9D,UAAU,uBAAuB;IAC/B,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,OAAO,EAAE,cAAc,CAAC;IACxB,kCAAkC;IAClC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IACpC,yDAAyD;IACzD,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,UAAU,wBAAwB;IAChC;;;OAGG;IACH,GAAG,EAAE,OAAO,CAAC;IACb,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,6BAA6B;IAC7B,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IACpC,mBAAmB;IACnB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,iBAAiB;IACjB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,kBAAkB;IAClB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,iBAAiB;IACjB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,uCAAuC;IACvC,QAAQ,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;IAC/C,kBAAkB;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,yCAAyC;IACzC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,yBAAyB;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,oCAAoC;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,qEAAqE;IACrE,KAAK,CAAC,EAAE,qBAAqB,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,uBAAuB,CAAC,CAoIlC"}
@@ -13,6 +13,8 @@ import { isExtendedFSAdapter } from "../../platform/adapters/fs/wrapper.js";
13
13
  import { getConfig } from "../../config/loader.js";
14
14
  import { timeAsync } from "./request-lifecycle.js";
15
15
  import { defaultDiscoveryCache, findLocalProjectPath, } from "./local-project-discovery.js";
16
+ import { isProxyTrusted } from "../utils/proxy-trust.js";
17
+ import { getHostEnv } from "../../platform/compat/process.js";
16
18
  const baseLogger = getBaseLogger("SERVER");
17
19
  const logger = baseLogger.component("adapter-factory");
18
20
  /**
@@ -29,7 +31,22 @@ export async function resolveAdapter(opts) {
29
31
  // Check if this is a local project.
30
32
  // In proxy mode, skip local discovery unless there's an explicit header path override —
31
33
  // the standard directories (data/projects/, projects/) don't exist in k8s.
32
- const trustedHeaderProjectPath = opts.isProxyMode ? opts.headerProjectPath : undefined;
34
+ //
35
+ // SECURITY: `x-project-path` is a client-controlled header. Honouring it from any
36
+ // request would let an attacker reaching the runtime directly aim project discovery
37
+ // (and therefore `/_veryfront/fs/...`) at arbitrary filesystem paths (VULN-SRV-3).
38
+ // Only read it when the request is proxy-trusted: either the operator opted in via
39
+ // VERYFRONT_TRUST_FORWARDED_HEADERS=1, or the request carries a dispatch JWS that
40
+ // verifies against CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY. Mere header presence is
41
+ // NOT sufficient — a direct-access attacker could otherwise spoof `x-project-path`
42
+ // by attaching any value in `x-veryfront-dispatch-jws`.
43
+ const publicKeyPem = opts.adapter.env.get("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY") ??
44
+ getHostEnv("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY");
45
+ const proxyTrusted = opts.isProxyMode &&
46
+ (await isProxyTrusted(opts.req, { publicKeyPem }));
47
+ const trustedHeaderProjectPath = proxyTrusted
48
+ ? opts.req.headers.get("x-project-path")?.trim() || undefined
49
+ : undefined;
33
50
  const shouldCheckLocalPath = opts.projectSlug && (!opts.isProxyMode || trustedHeaderProjectPath);
34
51
  const localProjectPath = shouldCheckLocalPath
35
52
  ? await findLocalProjectPath(opts.projectSlug, opts.adapter, trustedHeaderProjectPath, cache)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAK7D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AA+BpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AA+DrE,OAAO,EAAE,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAMtF,2CAA2C;AAC3C,eAAO,MAAM,aAAa,uqBAkChB,CAAC;AAEX,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,mDAAmD;IACnD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA0CD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,mBAAwB,GAC7B;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,iBAAiB,CAAA;CAAE,CAuB5D;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;CAC/C;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,qBAAsC,GAC3C,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAganE;AAGD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAK7D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AA+BpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AA+DrE,OAAO,EAAE,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAMtF,2CAA2C;AAC3C,eAAO,MAAM,aAAa,uqBAkChB,CAAC;AAEX,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,mDAAmD;IACnD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA0CD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,mBAAwB,GAC7B;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,iBAAiB,CAAA;CAAE,CAuB5D;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;CAC/C;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,qBAAsC,GAC3C,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAmanE;AAGD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC"}
@@ -352,8 +352,12 @@ export function createVeryfrontHandler(projectDir, adapter, opts = { projectDir
352
352
  if (response)
353
353
  return response;
354
354
  }
355
- // Resolve adapter and config for project
355
+ // Resolve adapter and config for project.
356
+ // Note: `x-project-path` is NOT forwarded via `headers.projectPath` anymore;
357
+ // `resolveAdapter` reads it directly from `req` and only honours it when the
358
+ // request is proxy-trusted (see isProxyTrusted).
356
359
  const adapterRes = await resolveAdapter({
360
+ req,
357
361
  projectDir,
358
362
  adapter,
359
363
  config,
@@ -365,7 +369,6 @@ export function createVeryfrontHandler(projectDir, adapter, opts = { projectDir
365
369
  branch: reqCtx.branch,
366
370
  environmentName: projectRes.environmentName,
367
371
  parsedDomain: projectRes.parsedDomain,
368
- headerProjectPath: headers.projectPath,
369
372
  isProxyMode,
370
373
  });
371
374
  // Resolve environment and validate
@@ -1 +1 @@
1
- {"version":3,"file":"page-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/services/rsc/orchestrators/page-handler.ts"],"names":[],"mappings":"AAEA,qBAAa,WAAW;IACtB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ;IAQjF,OAAO,CAAC,SAAS;CAwElB"}
1
+ {"version":3,"file":"page-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/services/rsc/orchestrators/page-handler.ts"],"names":[],"mappings":"AAuBA,qBAAa,WAAW;IACtB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ;IAQjF,OAAO,CAAC,SAAS;CAyElB"}
@@ -1,4 +1,24 @@
1
1
  import { buildNonceAttribute } from "../../../../html/html-escape.js";
2
+ /**
3
+ * Serialize a value as a JSON string literal that is safe to embed inside an
4
+ * inline HTML <script>. JSON already escapes quotes, backslashes, and control
5
+ * characters; we additionally escape:
6
+ * - `<` / `>` so `</script>`, `<!--`, and `<script` cannot appear literally,
7
+ * - `&` as defense-in-depth against reparsing contexts (e.g. HTML entity
8
+ * re-decoding in some legacy paths),
9
+ * - U+2028 / U+2029 which are valid JSON but terminate JS string literals
10
+ * in older browsers.
11
+ *
12
+ * See VULN-INJ-1 in the security audit.
13
+ */
14
+ function jsonForScript(value) {
15
+ return JSON.stringify(value)
16
+ .replace(/</g, "\\u003c")
17
+ .replace(/>/g, "\\u003e")
18
+ .replace(/&/g, "\\u0026")
19
+ .replace(/\u2028/g, "\\u2028")
20
+ .replace(/\u2029/g, "\\u2029");
21
+ }
2
22
  export class PageHandler {
3
23
  handle(pathname, searchParams, nonce) {
4
24
  const html = this.buildHtml(pathname, searchParams, nonce);
@@ -10,6 +30,7 @@ export class PageHandler {
10
30
  const queryString = searchParams.toString();
11
31
  const renderUrl = `/_veryfront/rsc/render${pathname}${queryString ? `?${queryString}` : ""}`;
12
32
  const nonceAttr = buildNonceAttribute(nonce);
33
+ const renderUrlJs = jsonForScript(renderUrl);
13
34
  return `<!DOCTYPE html>
14
35
  <html lang="en">
15
36
  <head>
@@ -57,7 +78,7 @@ export class PageHandler {
57
78
  }
58
79
 
59
80
  (async () => {
60
- const renderUrl = '${renderUrl}';
81
+ const renderUrl = ${renderUrlJs};
61
82
  const payload =
62
83
  (await fetchPayload(renderUrl)) ??
63
84
  (await fetchPayload('/_veryfront/rsc/payload')) ??
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Proxy trust boundary.
3
+ *
4
+ * Forwarded headers such as `x-forwarded-host` and `x-project-path` must only be
5
+ * honoured when the request is known to come from a trusted upstream proxy.
6
+ * Any other treatment lets an attacker reaching the runtime directly spoof the
7
+ * origin host or point project discovery at arbitrary filesystem paths.
8
+ *
9
+ * A request is considered proxy-trusted when either:
10
+ * 1. The operator has opted in via `VERYFRONT_TRUST_FORWARDED_HEADERS=1`
11
+ * (strict "1" match — "true", "yes", whitespace-padded values do NOT count
12
+ * so misconfiguration fails closed); or
13
+ * 2. The request carries a valid `x-veryfront-dispatch-jws` header that
14
+ * cryptographically verifies against the configured control-plane public
15
+ * key and whose `iat`/`exp` claims are within the allowed freshness
16
+ * window. Presence alone is NOT trusted because the proxy does not strip
17
+ * this header from untrusted inbound requests (it has to pass through to
18
+ * the channel-invoke / channel-assistants handlers unchanged), so a
19
+ * direct-access attacker could otherwise set any value and promote
20
+ * forwarded-header spoofing.
21
+ *
22
+ * @module server/utils/proxy-trust
23
+ */
24
+ export interface ProxyTrustOptions {
25
+ /**
26
+ * PEM-encoded Ed25519 public key used to verify `x-veryfront-dispatch-jws`.
27
+ * When absent, the dispatch-JWS trust signal is disabled (fails closed) and
28
+ * only the operator opt-in env var can unlock proxy trust.
29
+ */
30
+ publicKeyPem?: string;
31
+ }
32
+ export declare function isProxyTrusted(req: Request, options?: ProxyTrustOptions): Promise<boolean>;
33
+ //# sourceMappingURL=proxy-trust.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-trust.d.ts","sourceRoot":"","sources":["../../../../src/src/server/utils/proxy-trust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,OAAO,CAAC,CAalB"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Proxy trust boundary.
3
+ *
4
+ * Forwarded headers such as `x-forwarded-host` and `x-project-path` must only be
5
+ * honoured when the request is known to come from a trusted upstream proxy.
6
+ * Any other treatment lets an attacker reaching the runtime directly spoof the
7
+ * origin host or point project discovery at arbitrary filesystem paths.
8
+ *
9
+ * A request is considered proxy-trusted when either:
10
+ * 1. The operator has opted in via `VERYFRONT_TRUST_FORWARDED_HEADERS=1`
11
+ * (strict "1" match — "true", "yes", whitespace-padded values do NOT count
12
+ * so misconfiguration fails closed); or
13
+ * 2. The request carries a valid `x-veryfront-dispatch-jws` header that
14
+ * cryptographically verifies against the configured control-plane public
15
+ * key and whose `iat`/`exp` claims are within the allowed freshness
16
+ * window. Presence alone is NOT trusted because the proxy does not strip
17
+ * this header from untrusted inbound requests (it has to pass through to
18
+ * the channel-invoke / channel-assistants handlers unchanged), so a
19
+ * direct-access attacker could otherwise set any value and promote
20
+ * forwarded-header spoofing.
21
+ *
22
+ * @module server/utils/proxy-trust
23
+ */
24
+ import { verifyDispatchJwsSignature } from "../../channels/control-plane.js";
25
+ import { getHostEnv } from "../../platform/compat/process.js";
26
+ const DISPATCH_JWS_HEADER = "x-veryfront-dispatch-jws";
27
+ const MAX_DISPATCH_SIGNATURE_AGE_SECONDS = 60;
28
+ export async function isProxyTrusted(req, options = {}) {
29
+ if (getHostEnv("VERYFRONT_TRUST_FORWARDED_HEADERS") === "1")
30
+ return true;
31
+ const jws = req.headers.get(DISPATCH_JWS_HEADER);
32
+ if (!jws)
33
+ return false;
34
+ const { publicKeyPem } = options;
35
+ if (!publicKeyPem)
36
+ return false;
37
+ return verifyDispatchJwsSignature(jws, {
38
+ publicKeyPem,
39
+ maxAgeSeconds: MAX_DISPATCH_SIGNATURE_AGE_SECONDS,
40
+ });
41
+ }
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.229";
1
+ export declare const VERSION = "0.1.230";
2
2
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.229";
3
+ export const VERSION = "0.1.230";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.229",
3
+ "version": "0.1.230",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.229",
3
+ "version": "0.1.230",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -0,0 +1,121 @@
1
+ export interface HostedChildLifecycleTerminalState {
2
+ status: "completed" | "failed" | "cancelled";
3
+ usage?: {
4
+ inputTokens?: number;
5
+ outputTokens?: number;
6
+ totalTokens?: number;
7
+ };
8
+ terminalErrorCode?: string | null;
9
+ terminalErrorMessage?: string | null;
10
+ }
11
+
12
+ export interface HostedChildLifecycleCompletedState
13
+ extends Omit<HostedChildLifecycleTerminalState, "status"> {
14
+ status: "completed";
15
+ }
16
+
17
+ export interface HostedChildLifecycleAdapter {
18
+ pending?: () => Promise<void> | void;
19
+ running?: () => Promise<void> | void;
20
+ completed?: (
21
+ terminalState: HostedChildLifecycleTerminalState,
22
+ ) => Promise<void> | void;
23
+ failed?: (
24
+ terminalState: HostedChildLifecycleTerminalState,
25
+ ) => Promise<void> | void;
26
+ cancelled?: (
27
+ terminalState: HostedChildLifecycleTerminalState,
28
+ ) => Promise<void> | void;
29
+ }
30
+
31
+ export interface HostedChildLifecycleErrorState
32
+ extends Omit<HostedChildLifecycleTerminalState, "status"> {
33
+ status: "failed" | "cancelled";
34
+ }
35
+
36
+ export interface HostedChildLifecycleRunnerOptions<TResult> {
37
+ adapter: HostedChildLifecycleAdapter;
38
+ execute: () => Promise<TResult> | TResult;
39
+ resolveCompletedState?: (
40
+ result: TResult,
41
+ ) =>
42
+ | Promise<HostedChildLifecycleCompletedState>
43
+ | HostedChildLifecycleCompletedState;
44
+ resolveErrorState: (
45
+ error: unknown,
46
+ ) =>
47
+ | Promise<HostedChildLifecycleErrorState>
48
+ | HostedChildLifecycleErrorState;
49
+ onLifecycleError?: (error: unknown) => Promise<void> | void;
50
+ }
51
+
52
+ export type HostedChildLifecycleRunResult<TResult> =
53
+ | {
54
+ status: "completed";
55
+ result: TResult;
56
+ terminalState: HostedChildLifecycleTerminalState;
57
+ }
58
+ | {
59
+ status: "failed" | "cancelled";
60
+ error: unknown;
61
+ terminalState: HostedChildLifecycleTerminalState;
62
+ };
63
+
64
+ async function dispatchTerminalState(
65
+ adapter: HostedChildLifecycleAdapter,
66
+ terminalState: HostedChildLifecycleTerminalState,
67
+ ): Promise<void> {
68
+ if (terminalState.status === "cancelled") {
69
+ await adapter.cancelled?.(terminalState);
70
+ return;
71
+ }
72
+
73
+ if (terminalState.status === "failed") {
74
+ await adapter.failed?.(terminalState);
75
+ return;
76
+ }
77
+
78
+ await adapter.completed?.(terminalState);
79
+ }
80
+
81
+ export async function runHostedChildLifecycle<TResult>(
82
+ options: HostedChildLifecycleRunnerOptions<TResult>,
83
+ ): Promise<HostedChildLifecycleRunResult<TResult>> {
84
+ await options.adapter.pending?.();
85
+ await options.adapter.running?.();
86
+
87
+ let result: TResult;
88
+ try {
89
+ result = await options.execute();
90
+ } catch (error) {
91
+ const terminalState = await options.resolveErrorState(error);
92
+
93
+ try {
94
+ await dispatchTerminalState(options.adapter, terminalState);
95
+ } catch (lifecycleError) {
96
+ if (options.onLifecycleError) {
97
+ await options.onLifecycleError(lifecycleError);
98
+ } else {
99
+ throw lifecycleError;
100
+ }
101
+ }
102
+
103
+ return {
104
+ status: terminalState.status,
105
+ error,
106
+ terminalState,
107
+ };
108
+ }
109
+
110
+ const terminalState = options.resolveCompletedState
111
+ ? await options.resolveCompletedState(result)
112
+ : { status: "completed" as const };
113
+
114
+ await dispatchTerminalState(options.adapter, terminalState);
115
+
116
+ return {
117
+ status: "completed",
118
+ result,
119
+ terminalState,
120
+ };
121
+ }
@@ -180,6 +180,13 @@ export {
180
180
  createAgUiBrowserResponseStream,
181
181
  type CreateAgUiBrowserResponseStreamInput,
182
182
  } from "./ag-ui-browser-response-stream.js";
183
+ export {
184
+ type HostedChildLifecycleAdapter,
185
+ type HostedChildLifecycleRunnerOptions,
186
+ type HostedChildLifecycleRunResult,
187
+ type HostedChildLifecycleTerminalState,
188
+ runHostedChildLifecycle,
189
+ } from "./hosted-child-lifecycle.js";
183
190
  export {
184
191
  type HostedLifecycleAdapter,
185
192
  type HostedLifecycleExecution,
@@ -294,6 +294,58 @@ export async function listRuntimeAgents(
294
294
  return RuntimeAgentListResponseSchema.parse({ agents });
295
295
  }
296
296
 
297
+ /**
298
+ * Verify the Ed25519 signature of a dispatch JWS and the recency of its
299
+ * timestamps, without binding to a particular request body or audience.
300
+ *
301
+ * This is intentionally weaker than {@link verifyDispatchJws}: it answers
302
+ * "was this JWS minted by a holder of the control-plane private key and is it
303
+ * still fresh?" and is used as a trust signal in code paths (proxy-trust,
304
+ * adapter selection) that don't yet have access to the authoritative request
305
+ * body or project audience. Callers that consume request payloads MUST still
306
+ * call {@link verifyDispatchJws} / {@link verifyControlPlaneJws} to bind the
307
+ * signature to the body and project.
308
+ *
309
+ * Returns true iff the signature verifies and `iat`/`exp` are within the
310
+ * allowed skew and max-age window. All other failures (including parsing
311
+ * errors) resolve to false so callers can treat the signal as present-but-not-
312
+ * proven without raising.
313
+ */
314
+ export async function verifyDispatchJwsSignature(
315
+ jws: string,
316
+ options: {
317
+ publicKeyPem: string;
318
+ maxAgeSeconds: number;
319
+ },
320
+ ): Promise<boolean> {
321
+ try {
322
+ const parts = jws.split(".");
323
+ if (parts.length !== 3) return false;
324
+ const [encodedHeader, encodedPayload, encodedSignature] = parts;
325
+ if (!encodedHeader || !encodedPayload || !encodedSignature) return false;
326
+
327
+ compactJwsHeaderSchema.parse(parseCompactJwsPart(encodedHeader));
328
+ const claims = dispatchClaimsSchema.parse(parseCompactJwsPart(encodedPayload));
329
+
330
+ const signingInput = new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`);
331
+ const signature = base64urlDecodeToBytes(encodedSignature);
332
+ const publicKey = await importEd25519PublicKey(options.publicKeyPem);
333
+ const verified = await dntShim.crypto.subtle.verify("Ed25519", publicKey, signature, signingInput);
334
+ if (!verified) return false;
335
+
336
+ if (claims.iss !== "veryfront-api") return false;
337
+
338
+ const now = Math.floor(Date.now() / 1000);
339
+ if (claims.exp <= now) return false;
340
+ if (claims.iat > now + SIGNATURE_SKEW_SECONDS) return false;
341
+ if (now - claims.iat > options.maxAgeSeconds) return false;
342
+
343
+ return true;
344
+ } catch {
345
+ return false;
346
+ }
347
+ }
348
+
297
349
  export async function verifyDispatchJws(
298
350
  jws: string,
299
351
  body: string,
@@ -153,7 +153,7 @@ export async function listChannelAssistants(
153
153
 
154
154
  return ChannelAssistantsResponseSchema.parse({ assistants });
155
155
  }
156
- export { verifyDispatchJws } from "./control-plane.js";
156
+ export { verifyDispatchJws, verifyDispatchJwsSignature } from "./control-plane.js";
157
157
 
158
158
  function normalizeConversationPart(
159
159
  part: z.infer<typeof rawHistoryPartSchema>,
@@ -29,6 +29,8 @@ import {
29
29
  import { getPingIntervalMs, startPingInterval, stopPingInterval } from "./hmr-ping-keepalive.js";
30
30
  import { broadcastUpdate, getMetrics } from "./hmr-message-router.js";
31
31
  import { getEffectiveRequestHost } from "../../utils/request-host.js";
32
+ import { isProxyTrusted } from "../../utils/proxy-trust.js";
33
+ import { getHostEnv } from "../../../platform/compat/process.js";
32
34
 
33
35
  const logger = serverLogger.component("hmr-handler");
34
36
 
@@ -89,14 +91,24 @@ export class HMRHandler extends BaseHandler {
89
91
  });
90
92
  }
91
93
 
92
- handle(req: Request, ctx: HandlerContext): Promise<HandlerResult> {
93
- if (!this.shouldHandle(req, ctx)) return Promise.resolve(this.continue());
94
+ async handle(req: Request, ctx: HandlerContext): Promise<HandlerResult> {
95
+ if (!this.shouldHandle(req, ctx)) return this.continue();
94
96
 
95
97
  const url = new URL(req.url);
96
98
  const queryEnv = url.searchParams.get("x-environment");
97
99
  const isPreviewMode = ctx.requestContext?.mode === "preview" || queryEnv === "preview";
98
100
  const isLocal = !!ctx.isLocalProject;
99
- const host = getEffectiveRequestHost(req, url);
101
+ // SECURITY: x-forwarded-host is client-controlled unless we trust the upstream proxy.
102
+ // Honouring it unconditionally lets any remote client present `x-forwarded-host: localhost`
103
+ // and unlock the localhost short-circuit that opens HMR (VULN-SRV-4). Only consult
104
+ // forwarded headers when the request is proxy-trusted; otherwise use Host / url.host.
105
+ // Proxy trust requires a verifiable dispatch JWS (or operator opt-in) — mere header
106
+ // presence is not enough, since `x-veryfront-dispatch-jws` is not stripped on ingress.
107
+ const publicKeyPem = ctx.adapter?.env?.get("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY") ??
108
+ getHostEnv("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY");
109
+ const host = (await isProxyTrusted(req, { publicKeyPem }))
110
+ ? getEffectiveRequestHost(req, url)
111
+ : (req.headers.get("host") ?? url.host);
100
112
  const isLocalhost = isLocalDevHost(host);
101
113
 
102
114
  if (!isPreviewMode && !isLocal && !isLocalhost) {
@@ -109,7 +121,7 @@ export class HMRHandler extends BaseHandler {
109
121
  isLocal,
110
122
  isLocalhost,
111
123
  });
112
- return Promise.resolve(this.continue());
124
+ return this.continue();
113
125
  }
114
126
 
115
127
  HMRHandler.initialize();
@@ -127,29 +139,25 @@ export class HMRHandler extends BaseHandler {
127
139
  }
128
140
 
129
141
  if (req.headers.get("upgrade")?.toLowerCase() !== "websocket") {
130
- return Promise.resolve(
131
- this.respond(
132
- new Response(
133
- JSON.stringify({
134
- status: "ok",
135
- clients: getClientCount(),
136
- clientDetails: getClientDetails(),
137
- metrics: {
138
- ...getMetrics(),
139
- reloadNotifierMetrics: ReloadNotifier.getMetrics(),
140
- },
141
- message: "HMR WebSocket endpoint - connect via WebSocket",
142
- }),
143
- { headers: { "content-type": "application/json" } },
144
- ),
142
+ return this.respond(
143
+ new Response(
144
+ JSON.stringify({
145
+ status: "ok",
146
+ clients: getClientCount(),
147
+ clientDetails: getClientDetails(),
148
+ metrics: {
149
+ ...getMetrics(),
150
+ reloadNotifierMetrics: ReloadNotifier.getMetrics(),
151
+ },
152
+ message: "HMR WebSocket endpoint - connect via WebSocket",
153
+ }),
154
+ { headers: { "content-type": "application/json" } },
145
155
  ),
146
156
  );
147
157
  }
148
158
 
149
159
  if (!ctx.adapter?.server) {
150
- return Promise.resolve(
151
- this.respond(new Response("WebSocket not supported", { status: 501 })),
152
- );
160
+ return this.respond(new Response("WebSocket not supported", { status: 501 }));
153
161
  }
154
162
 
155
163
  try {
@@ -234,12 +242,10 @@ export class HMRHandler extends BaseHandler {
234
242
  totalClients: getClientCount(),
235
243
  });
236
244
 
237
- return Promise.resolve(this.respond(response));
245
+ return this.respond(response);
238
246
  } catch (error) {
239
247
  logger.error("WebSocket upgrade failed", { error });
240
- return Promise.resolve(
241
- this.respond(new Response("WebSocket upgrade failed", { status: 500 })),
242
- );
248
+ return this.respond(new Response("WebSocket upgrade failed", { status: 500 }));
243
249
  }
244
250
  }
245
251
 
@@ -21,6 +21,8 @@ import {
21
21
  type ProjectDiscoveryCache,
22
22
  } from "./local-project-discovery.js";
23
23
  import type { ParsedDomain } from "../utils/domain-parser.js";
24
+ import { isProxyTrusted } from "../utils/proxy-trust.js";
25
+ import { getHostEnv } from "../../platform/compat/process.js";
24
26
 
25
27
  const baseLogger = getBaseLogger("SERVER");
26
28
 
@@ -38,6 +40,11 @@ interface AdapterResolutionResult {
38
40
  }
39
41
 
40
42
  interface AdapterResolutionOptions {
43
+ /**
44
+ * Inbound request. Used to determine whether forwarded headers such as
45
+ * `x-project-path` can be trusted (see {@link isProxyTrusted}).
46
+ */
47
+ req: Request;
41
48
  /** Base project directory */
42
49
  projectDir: string;
43
50
  /** Base adapter */
@@ -60,8 +67,6 @@ interface AdapterResolutionOptions {
60
67
  environmentName: string | undefined;
61
68
  /** Parsed domain info */
62
69
  parsedDomain: ParsedDomain;
63
- /** Project path from header */
64
- headerProjectPath: string | undefined;
65
70
  /** Whether running in proxy mode */
66
71
  isProxyMode: boolean;
67
72
  /** Optional injectable cache (defaults to module-level singleton) */
@@ -86,7 +91,22 @@ export async function resolveAdapter(
86
91
  // Check if this is a local project.
87
92
  // In proxy mode, skip local discovery unless there's an explicit header path override —
88
93
  // the standard directories (data/projects/, projects/) don't exist in k8s.
89
- const trustedHeaderProjectPath = opts.isProxyMode ? opts.headerProjectPath : undefined;
94
+ //
95
+ // SECURITY: `x-project-path` is a client-controlled header. Honouring it from any
96
+ // request would let an attacker reaching the runtime directly aim project discovery
97
+ // (and therefore `/_veryfront/fs/...`) at arbitrary filesystem paths (VULN-SRV-3).
98
+ // Only read it when the request is proxy-trusted: either the operator opted in via
99
+ // VERYFRONT_TRUST_FORWARDED_HEADERS=1, or the request carries a dispatch JWS that
100
+ // verifies against CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY. Mere header presence is
101
+ // NOT sufficient — a direct-access attacker could otherwise spoof `x-project-path`
102
+ // by attaching any value in `x-veryfront-dispatch-jws`.
103
+ const publicKeyPem = opts.adapter.env.get("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY") ??
104
+ getHostEnv("CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY");
105
+ const proxyTrusted = opts.isProxyMode &&
106
+ (await isProxyTrusted(opts.req, { publicKeyPem }));
107
+ const trustedHeaderProjectPath = proxyTrusted
108
+ ? opts.req.headers.get("x-project-path")?.trim() || undefined
109
+ : undefined;
90
110
  const shouldCheckLocalPath = opts.projectSlug && (!opts.isProxyMode || trustedHeaderProjectPath);
91
111
  const localProjectPath = shouldCheckLocalPath
92
112
  ? await findLocalProjectPath(opts.projectSlug!, opts.adapter, trustedHeaderProjectPath, cache)
@@ -520,8 +520,12 @@ export function createVeryfrontHandler(
520
520
  if (response) return response;
521
521
  }
522
522
 
523
- // Resolve adapter and config for project
523
+ // Resolve adapter and config for project.
524
+ // Note: `x-project-path` is NOT forwarded via `headers.projectPath` anymore;
525
+ // `resolveAdapter` reads it directly from `req` and only honours it when the
526
+ // request is proxy-trusted (see isProxyTrusted).
524
527
  const adapterRes = await resolveAdapter({
528
+ req,
525
529
  projectDir,
526
530
  adapter,
527
531
  config,
@@ -533,7 +537,6 @@ export function createVeryfrontHandler(
533
537
  branch: reqCtx.branch,
534
538
  environmentName: projectRes.environmentName,
535
539
  parsedDomain: projectRes.parsedDomain,
536
- headerProjectPath: headers.projectPath,
537
540
  isProxyMode,
538
541
  });
539
542
 
@@ -1,5 +1,26 @@
1
1
  import { buildNonceAttribute } from "../../../../html/html-escape.js";
2
2
 
3
+ /**
4
+ * Serialize a value as a JSON string literal that is safe to embed inside an
5
+ * inline HTML <script>. JSON already escapes quotes, backslashes, and control
6
+ * characters; we additionally escape:
7
+ * - `<` / `>` so `</script>`, `<!--`, and `<script` cannot appear literally,
8
+ * - `&` as defense-in-depth against reparsing contexts (e.g. HTML entity
9
+ * re-decoding in some legacy paths),
10
+ * - U+2028 / U+2029 which are valid JSON but terminate JS string literals
11
+ * in older browsers.
12
+ *
13
+ * See VULN-INJ-1 in the security audit.
14
+ */
15
+ function jsonForScript(value: unknown): string {
16
+ return JSON.stringify(value)
17
+ .replace(/</g, "\\u003c")
18
+ .replace(/>/g, "\\u003e")
19
+ .replace(/&/g, "\\u0026")
20
+ .replace(/\u2028/g, "\\u2028")
21
+ .replace(/\u2029/g, "\\u2029");
22
+ }
23
+
3
24
  export class PageHandler {
4
25
  handle(pathname: string, searchParams: URLSearchParams, nonce?: string): Response {
5
26
  const html = this.buildHtml(pathname, searchParams, nonce);
@@ -13,6 +34,7 @@ export class PageHandler {
13
34
  const queryString = searchParams.toString();
14
35
  const renderUrl = `/_veryfront/rsc/render${pathname}${queryString ? `?${queryString}` : ""}`;
15
36
  const nonceAttr = buildNonceAttribute(nonce);
37
+ const renderUrlJs = jsonForScript(renderUrl);
16
38
 
17
39
  return `<!DOCTYPE html>
18
40
  <html lang="en">
@@ -61,7 +83,7 @@ export class PageHandler {
61
83
  }
62
84
 
63
85
  (async () => {
64
- const renderUrl = '${renderUrl}';
86
+ const renderUrl = ${renderUrlJs};
65
87
  const payload =
66
88
  (await fetchPayload(renderUrl)) ??
67
89
  (await fetchPayload('/_veryfront/rsc/payload')) ??
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Proxy trust boundary.
3
+ *
4
+ * Forwarded headers such as `x-forwarded-host` and `x-project-path` must only be
5
+ * honoured when the request is known to come from a trusted upstream proxy.
6
+ * Any other treatment lets an attacker reaching the runtime directly spoof the
7
+ * origin host or point project discovery at arbitrary filesystem paths.
8
+ *
9
+ * A request is considered proxy-trusted when either:
10
+ * 1. The operator has opted in via `VERYFRONT_TRUST_FORWARDED_HEADERS=1`
11
+ * (strict "1" match — "true", "yes", whitespace-padded values do NOT count
12
+ * so misconfiguration fails closed); or
13
+ * 2. The request carries a valid `x-veryfront-dispatch-jws` header that
14
+ * cryptographically verifies against the configured control-plane public
15
+ * key and whose `iat`/`exp` claims are within the allowed freshness
16
+ * window. Presence alone is NOT trusted because the proxy does not strip
17
+ * this header from untrusted inbound requests (it has to pass through to
18
+ * the channel-invoke / channel-assistants handlers unchanged), so a
19
+ * direct-access attacker could otherwise set any value and promote
20
+ * forwarded-header spoofing.
21
+ *
22
+ * @module server/utils/proxy-trust
23
+ */
24
+
25
+ import { verifyDispatchJwsSignature } from "../../channels/control-plane.js";
26
+ import { getHostEnv } from "../../platform/compat/process.js";
27
+
28
+ const DISPATCH_JWS_HEADER = "x-veryfront-dispatch-jws";
29
+ const MAX_DISPATCH_SIGNATURE_AGE_SECONDS = 60;
30
+
31
+ export interface ProxyTrustOptions {
32
+ /**
33
+ * PEM-encoded Ed25519 public key used to verify `x-veryfront-dispatch-jws`.
34
+ * When absent, the dispatch-JWS trust signal is disabled (fails closed) and
35
+ * only the operator opt-in env var can unlock proxy trust.
36
+ */
37
+ publicKeyPem?: string;
38
+ }
39
+
40
+ export async function isProxyTrusted(
41
+ req: Request,
42
+ options: ProxyTrustOptions = {},
43
+ ): Promise<boolean> {
44
+ if (getHostEnv("VERYFRONT_TRUST_FORWARDED_HEADERS") === "1") return true;
45
+
46
+ const jws = req.headers.get(DISPATCH_JWS_HEADER);
47
+ if (!jws) return false;
48
+
49
+ const { publicKeyPem } = options;
50
+ if (!publicKeyPem) return false;
51
+
52
+ return verifyDispatchJwsSignature(jws, {
53
+ publicKeyPem,
54
+ maxAgeSeconds: MAX_DISPATCH_SIGNATURE_AGE_SECONDS,
55
+ });
56
+ }
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.229";
3
+ export const VERSION = "0.1.230";