mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
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
This commit is contained in:
@@ -24,13 +24,13 @@ export function videoGenerateEndpoint(baseUrl: string): string {
|
||||
// ---- Async Task Query ----
|
||||
|
||||
export function taskEndpoint(baseUrl: string, taskId: string): string {
|
||||
return `${baseUrl}/api/v1/tasks/${taskId}`;
|
||||
return `${baseUrl}/api/v1/tasks/${encodeURIComponent(taskId)}`;
|
||||
}
|
||||
|
||||
// ---- Application (Agent / Workflow) ----
|
||||
|
||||
export function appCompletionEndpoint(baseUrl: string, appId: string): string {
|
||||
return `${baseUrl}/api/v1/apps/${appId}/completion`;
|
||||
return `${baseUrl}/api/v1/apps/${encodeURIComponent(appId)}/completion`;
|
||||
}
|
||||
|
||||
// ---- Memory (DashScope v2) ----
|
||||
@@ -48,7 +48,7 @@ export function memoryListEndpoint(baseUrl: string): string {
|
||||
}
|
||||
|
||||
export function memoryNodeEndpoint(baseUrl: string, nodeId: string): string {
|
||||
return `${baseUrl}/api/v2/apps/memory/memory_nodes/${nodeId}`;
|
||||
return `${baseUrl}/api/v2/apps/memory/memory_nodes/${encodeURIComponent(nodeId)}`;
|
||||
}
|
||||
|
||||
// ---- Speech Synthesis (TTS) ----
|
||||
@@ -70,7 +70,7 @@ export function profileSchemaEndpoint(baseUrl: string): string {
|
||||
}
|
||||
|
||||
export function userProfileEndpoint(baseUrl: string, schemaId: string): string {
|
||||
return `${baseUrl}/api/v2/apps/memory/profile_schemas/${schemaId}/profiles`;
|
||||
return `${baseUrl}/api/v2/apps/memory/profile_schemas/${encodeURIComponent(schemaId)}/profiles`;
|
||||
}
|
||||
|
||||
// ---- MCP Services (Streamable HTTP) ----
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { BailianError } from "../errors/base.ts";
|
||||
import { ExitCode } from "../errors/codes.ts";
|
||||
|
||||
export interface ServerSentEvent {
|
||||
event?: string;
|
||||
data: string;
|
||||
@@ -11,12 +14,20 @@ export async function* parseSSE(response: Response): AsyncGenerator<ServerSentEv
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
// Guard against a hostile or malfunctioning stream that never emits a newline
|
||||
// (or builds a single absurdly large event): bound the in-memory buffer so the
|
||||
// parser cannot be driven to exhaust process memory.
|
||||
const MAX_SSE_BUFFER = 16 * 1024 * 1024; // 16 MiB
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
if (buffer.length > MAX_SSE_BUFFER) {
|
||||
throw new BailianError("SSE stream exceeded the maximum buffer size.", ExitCode.GENERAL);
|
||||
}
|
||||
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
@@ -43,6 +54,12 @@ export async function* parseSSE(response: Response): AsyncGenerator<ServerSentEv
|
||||
switch (field) {
|
||||
case "data":
|
||||
event.data = event.data !== undefined ? `${event.data}\n${value}` : value;
|
||||
if (event.data.length > MAX_SSE_BUFFER) {
|
||||
throw new BailianError(
|
||||
"SSE event exceeded the maximum buffer size.",
|
||||
ExitCode.GENERAL,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "event":
|
||||
event.event = value;
|
||||
|
||||
Reference in New Issue
Block a user