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.
- package/esm/deno.js +1 -1
- package/esm/src/agent/hosted-child-lifecycle.d.ts +41 -0
- package/esm/src/agent/hosted-child-lifecycle.d.ts.map +1 -0
- package/esm/src/agent/hosted-child-lifecycle.js +47 -0
- package/esm/src/agent/index.d.ts +1 -0
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +1 -0
- package/esm/src/channels/control-plane.d.ts +21 -0
- package/esm/src/channels/control-plane.d.ts.map +1 -1
- package/esm/src/channels/control-plane.js +48 -0
- package/esm/src/channels/invoke.d.ts +1 -1
- package/esm/src/channels/invoke.d.ts.map +1 -1
- package/esm/src/channels/invoke.js +1 -1
- package/esm/src/server/handlers/preview/hmr.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/hmr.handler.js +21 -9
- package/esm/src/server/runtime-handler/adapter-factory.d.ts +5 -2
- package/esm/src/server/runtime-handler/adapter-factory.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/adapter-factory.js +18 -1
- package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/index.js +5 -2
- package/esm/src/server/services/rsc/orchestrators/page-handler.d.ts.map +1 -1
- package/esm/src/server/services/rsc/orchestrators/page-handler.js +22 -1
- package/esm/src/server/utils/proxy-trust.d.ts +33 -0
- package/esm/src/server/utils/proxy-trust.d.ts.map +1 -0
- package/esm/src/server/utils/proxy-trust.js +41 -0
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/agent/hosted-child-lifecycle.ts +121 -0
- package/src/src/agent/index.ts +7 -0
- package/src/src/channels/control-plane.ts +52 -0
- package/src/src/channels/invoke.ts +1 -1
- package/src/src/server/handlers/preview/hmr.handler.ts +32 -26
- package/src/src/server/runtime-handler/adapter-factory.ts +23 -3
- package/src/src/server/runtime-handler/index.ts +5 -2
- package/src/src/server/services/rsc/orchestrators/page-handler.ts +23 -1
- package/src/src/server/utils/proxy-trust.ts +56 -0
- package/src/src/utils/version-constant.ts +1 -1
package/esm/deno.js
CHANGED
|
@@ -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
|
+
}
|
package/esm/src/agent/index.d.ts
CHANGED
|
@@ -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":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/agent/index.ts"],"names":[],"mappings":"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"}
|
package/esm/src/agent/index.js
CHANGED
|
@@ -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;
|
|
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;
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
195
|
+
return this.respond(response);
|
|
184
196
|
}
|
|
185
197
|
catch (error) {
|
|
186
198
|
logger.error("WebSocket upgrade failed", { error });
|
|
187
|
-
return
|
|
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;
|
|
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
|
-
|
|
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,
|
|
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":"
|
|
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 =
|
|
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.
|
|
1
|
+
export declare const VERSION = "0.1.230";
|
|
2
2
|
//# sourceMappingURL=version-constant.d.ts.map
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -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
|
+
}
|
package/src/src/agent/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
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
|
|
245
|
+
return this.respond(response);
|
|
238
246
|
} catch (error) {
|
|
239
247
|
logger.error("WebSocket upgrade failed", { error });
|
|
240
|
-
return
|
|
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
|
-
|
|
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 =
|
|
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
|
+
}
|