fix(cowork-service): audit fixes — JWT auth forwarding + temperature:0 falsy bug
Marketplace proxy: - Forward Authorization header to platform-service for consumer/author routes that call requireAuth() — without this, all authenticated marketplace endpoints would 401 - Add 2 tests: auth header forwarding + omission when absent (100 tests, was 98) IPC intercept_llm handler: - Replace || with ?? for temperature and max_tokens — temperature:0 is a valid value (deterministic) but || treated it as falsy, passing undefined to the LLM router instead
This commit is contained in:
parent
bba26f2d5f
commit
2d70eeeeb0
@ -217,6 +217,43 @@ describe('marketplace proxy routes', () => {
|
||||
expect(mockFetch.mock.calls[0][0]).toContain('/marketplace/listings/mine');
|
||||
});
|
||||
|
||||
// ── Auth Header Forwarding ──
|
||||
|
||||
it('forwards Authorization header to platform-service for authenticated routes', async () => {
|
||||
mockFetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 201,
|
||||
json: async () => ({ install: { id: 'inst_1' } }),
|
||||
});
|
||||
|
||||
await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/marketplace/lst_123/install',
|
||||
headers: { authorization: 'Bearer test-jwt-token' },
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledOnce();
|
||||
const fetchHeaders = mockFetch.mock.calls[0][1].headers as Record<string, string>;
|
||||
expect(fetchHeaders['authorization']).toBe('Bearer test-jwt-token');
|
||||
expect(fetchHeaders['x-product-id']).toBe('clawcowork');
|
||||
});
|
||||
|
||||
it('omits Authorization header when no token present', async () => {
|
||||
mockFetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ items: [], total: 0 }),
|
||||
});
|
||||
|
||||
await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/marketplace',
|
||||
});
|
||||
|
||||
const fetchHeaders = mockFetch.mock.calls[0][1].headers as Record<string, string>;
|
||||
expect(fetchHeaders['authorization']).toBeUndefined();
|
||||
});
|
||||
|
||||
// ── Error Handling ──
|
||||
|
||||
it('returns 502 when platform-service is unreachable', async () => {
|
||||
|
||||
@ -24,15 +24,15 @@ import { ListListingsQuerySchema, ListInstallsQuerySchema, ListReviewsQuerySchem
|
||||
const platformUrl = config.PLATFORM_SERVICE_URL;
|
||||
|
||||
/** Build standard proxy headers for platform-service. */
|
||||
function proxyHeaders(req: { id: string; jwtPayload?: { sub: string } }): Record<string, string> {
|
||||
function proxyHeaders(req: { id: string; headers: Record<string, string | string[] | undefined>; jwtPayload?: { sub: string } }): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
'x-product-id': PRODUCT_ID,
|
||||
'x-request-id': req.id,
|
||||
'content-type': 'application/json',
|
||||
};
|
||||
if (req.jwtPayload?.sub) {
|
||||
headers['x-user-id'] = req.jwtPayload.sub;
|
||||
}
|
||||
// Forward JWT for authenticated routes (consumer + author endpoints)
|
||||
const auth = req.headers.authorization;
|
||||
if (typeof auth === 'string') headers['authorization'] = auth;
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
@ -111,8 +111,8 @@ bridge.onIncomingRequest(async (method, params) => {
|
||||
const result = await getLlmRouter().chat({
|
||||
messages: messages.map(m => ({ role: m.role as 'system' | 'user' | 'assistant', content: m.content })),
|
||||
model: (params.model as string) || undefined,
|
||||
temperature: (params.temperature as number) || undefined,
|
||||
max_tokens: (params.max_tokens as number) || undefined,
|
||||
temperature: (params.temperature as number) ?? undefined,
|
||||
max_tokens: (params.max_tokens as number) ?? undefined,
|
||||
});
|
||||
|
||||
// Record spend for budget tracking (best-effort)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user