Skip to content

Commit d24f203

Browse files
committed
fix(security): encode URL path segments and bound SSE buffering
- endpoints: encodeURIComponent the id segments (task_id, app_id, node_id, schema_id) interpolated into request URLs. task_id in particular comes from the server's async-submit response and is fetched back with the bearer token attached, so an unencoded value could steer the authenticated follow-up request to a different path on the host. - stream (SSE parser): cap the in-memory buffer (16 MiB). A stream that never emits a newline, or that builds one enormous event from many data: lines, could otherwise grow the buffer without bound and exhaust process memory. https://claude.ai/code/session_017ZGQCjwNQF5Pz96gLUnnG1
1 parent 3e4f1f0 commit d24f203

2 files changed

Lines changed: 21 additions & 4 deletions

File tree

packages/core/src/client/endpoints.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ export function videoGenerateEndpoint(baseUrl: string): string {
2424
// ---- Async Task Query ----
2525

2626
export function taskEndpoint(baseUrl: string, taskId: string): string {
27-
return `${baseUrl}/api/v1/tasks/${taskId}`;
27+
return `${baseUrl}/api/v1/tasks/${encodeURIComponent(taskId)}`;
2828
}
2929

3030
// ---- Application (Agent / Workflow) ----
3131

3232
export function appCompletionEndpoint(baseUrl: string, appId: string): string {
33-
return `${baseUrl}/api/v1/apps/${appId}/completion`;
33+
return `${baseUrl}/api/v1/apps/${encodeURIComponent(appId)}/completion`;
3434
}
3535

3636
// ---- Memory (DashScope v2) ----
@@ -48,7 +48,7 @@ export function memoryListEndpoint(baseUrl: string): string {
4848
}
4949

5050
export function memoryNodeEndpoint(baseUrl: string, nodeId: string): string {
51-
return `${baseUrl}/api/v2/apps/memory/memory_nodes/${nodeId}`;
51+
return `${baseUrl}/api/v2/apps/memory/memory_nodes/${encodeURIComponent(nodeId)}`;
5252
}
5353

5454
// ---- Speech Synthesis (TTS) ----
@@ -70,7 +70,7 @@ export function profileSchemaEndpoint(baseUrl: string): string {
7070
}
7171

7272
export function userProfileEndpoint(baseUrl: string, schemaId: string): string {
73-
return `${baseUrl}/api/v2/apps/memory/profile_schemas/${schemaId}/profiles`;
73+
return `${baseUrl}/api/v2/apps/memory/profile_schemas/${encodeURIComponent(schemaId)}/profiles`;
7474
}
7575

7676
// ---- MCP Services (Streamable HTTP) ----

packages/core/src/client/stream.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { BailianError } from "../errors/base.ts";
2+
import { ExitCode } from "../errors/codes.ts";
3+
14
export interface ServerSentEvent {
25
event?: string;
36
data: string;
@@ -11,12 +14,20 @@ export async function* parseSSE(response: Response): AsyncGenerator<ServerSentEv
1114
const decoder = new TextDecoder();
1215
let buffer = "";
1316

17+
// Guard against a hostile or malfunctioning stream that never emits a newline
18+
// (or builds a single absurdly large event): bound the in-memory buffer so the
19+
// parser cannot be driven to exhaust process memory.
20+
const MAX_SSE_BUFFER = 16 * 1024 * 1024; // 16 MiB
21+
1422
try {
1523
while (true) {
1624
const { done, value } = await reader.read();
1725
if (done) break;
1826

1927
buffer += decoder.decode(value, { stream: true });
28+
if (buffer.length > MAX_SSE_BUFFER) {
29+
throw new BailianError("SSE stream exceeded the maximum buffer size.", ExitCode.GENERAL);
30+
}
2031

2132
const lines = buffer.split("\n");
2233
buffer = lines.pop() || "";
@@ -43,6 +54,12 @@ export async function* parseSSE(response: Response): AsyncGenerator<ServerSentEv
4354
switch (field) {
4455
case "data":
4556
event.data = event.data !== undefined ? `${event.data}\n${value}` : value;
57+
if (event.data.length > MAX_SSE_BUFFER) {
58+
throw new BailianError(
59+
"SSE event exceeded the maximum buffer size.",
60+
ExitCode.GENERAL,
61+
);
62+
}
4663
break;
4764
case "event":
4865
event.event = value;

0 commit comments

Comments
 (0)