Skip to content

Commit 4618907

Browse files
committed
fix(cloud-agent): make Kilo prompt JSON parsing explicit
1 parent 403f728 commit 4618907

2 files changed

Lines changed: 79 additions & 21 deletions

File tree

services/cloud-agent-next/src/kilo-facade/user-kilo-facade.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,6 +1915,51 @@ describe('handleKiloFacadeRequest', () => {
19151915
expect(admitSubmittedMessage).not.toHaveBeenCalled();
19161916
});
19171917

1918+
it('rejects malformed or oversized prompt_async bodies before balance or admission side effects', async () => {
1919+
const validatePromptBalance = vi.fn();
1920+
const admitPrompt = vi.fn();
1921+
const supportedPromptBody = JSON.stringify({ parts: [{ type: 'text', text: 'hello' }] });
1922+
for (const requestInit of [
1923+
{ body: '{', headers: new Headers({ 'Content-Type': 'application/json' }) },
1924+
{
1925+
body: supportedPromptBody,
1926+
headers: new Headers({
1927+
'Content-Type': 'application/json',
1928+
'Content-Length': String(256 * 1024 + 1),
1929+
}),
1930+
},
1931+
{
1932+
body: supportedPromptBody.padEnd(256 * 1024 + 1, ' '),
1933+
headers: new Headers({ 'Content-Type': 'application/json' }),
1934+
},
1935+
]) {
1936+
const response = await handleKiloFacadeRequest({
1937+
request: new Request(`http://worker.test/kilo/session/${kiloSessionId}/prompt_async`, {
1938+
method: 'POST',
1939+
...requestInit,
1940+
}),
1941+
env: envStub(),
1942+
userId: 'usr_1',
1943+
authToken: 'validated-token',
1944+
deps: {
1945+
resolveRootSessionForKiloSession: vi.fn(async () => ({
1946+
cloudAgentSessionId: 'agent_cold',
1947+
})),
1948+
validatePromptBalance,
1949+
admitPrompt,
1950+
},
1951+
});
1952+
1953+
expect(response.status).toBe(400);
1954+
await expect(response.json()).resolves.toEqual({
1955+
error: 'KILO_BASIC_PROMPT_UNSUPPORTED',
1956+
message: 'Basic Kilo prompt body is not supported',
1957+
});
1958+
}
1959+
expect(validatePromptBalance).not.toHaveBeenCalled();
1960+
expect(admitPrompt).not.toHaveBeenCalled();
1961+
});
1962+
19181963
it('rejects prompt_async attempts to bypass public balance validation after owner resolution', async () => {
19191964
const env = envStub();
19201965
const validatePromptBalance = vi.fn();

services/cloud-agent-next/src/kilo-facade/user-kilo-facade.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -689,35 +689,48 @@ function promptPreflightError(error: unknown): Response {
689689
}
690690
}
691691

692-
async function readRequestJson(request: Request): Promise<unknown> {
692+
type ReadRequestJsonResult =
693+
| { success: true; value: unknown }
694+
| { success: false; response: Response };
695+
696+
async function readRequestJson(request: Request): Promise<ReadRequestJsonResult> {
693697
const declaredLength = request.headers.get('content-length');
694698
if (declaredLength !== null) {
695699
const bodyBytes = Number(declaredLength);
696700
if (!Number.isSafeInteger(bodyBytes) || bodyBytes > MAX_KILO_PROMPT_JSON_BYTES) {
697-
return facadeError(
698-
400,
699-
'KILO_BASIC_PROMPT_UNSUPPORTED',
700-
'Basic Kilo prompt body is not supported'
701-
);
701+
return {
702+
success: false,
703+
response: facadeError(
704+
400,
705+
'KILO_BASIC_PROMPT_UNSUPPORTED',
706+
'Basic Kilo prompt body is not supported'
707+
),
708+
};
702709
}
703710
}
704711
const response = new Response(request.body);
705712
const bytes = await readBoundedBody(response, MAX_KILO_PROMPT_JSON_BYTES);
706713
if (!bytes) {
707-
return facadeError(
708-
400,
709-
'KILO_BASIC_PROMPT_UNSUPPORTED',
710-
'Basic Kilo prompt body is not supported'
711-
);
714+
return {
715+
success: false,
716+
response: facadeError(
717+
400,
718+
'KILO_BASIC_PROMPT_UNSUPPORTED',
719+
'Basic Kilo prompt body is not supported'
720+
),
721+
};
712722
}
713723
try {
714-
return JSON.parse(new TextDecoder().decode(bytes));
724+
return { success: true, value: JSON.parse(new TextDecoder().decode(bytes)) };
715725
} catch {
716-
return facadeError(
717-
400,
718-
'KILO_BASIC_PROMPT_UNSUPPORTED',
719-
'Basic Kilo prompt body is not supported'
720-
);
726+
return {
727+
success: false,
728+
response: facadeError(
729+
400,
730+
'KILO_BASIC_PROMPT_UNSUPPORTED',
731+
'Basic Kilo prompt body is not supported'
732+
),
733+
};
721734
}
722735
}
723736

@@ -755,11 +768,11 @@ async function admitBasicPrompt(params: {
755768
cloudAgentSessionId: string;
756769
deps?: KiloFacadeRequestDeps;
757770
}): Promise<SessionMessageAdmissionResult | Response> {
758-
const value = await readRequestJson(params.request);
759-
if (value instanceof Response) {
760-
return value;
771+
const body = await readRequestJson(params.request);
772+
if (!body.success) {
773+
return body.response;
761774
}
762-
const parsed = parseBasicKiloPrompt(value);
775+
const parsed = parseBasicKiloPrompt(body.value);
763776
if (!parsed.success) {
764777
return facadeError(
765778
400,

0 commit comments

Comments
 (0)