From 2d70eeeeb024a4d00aeaacf2d6db151c29d1c52d Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 3 Apr 2026 01:32:34 -0700 Subject: [PATCH] =?UTF-8?q?fix(cowork-service):=20audit=20fixes=20?= =?UTF-8?q?=E2=80=94=20JWT=20auth=20forwarding=20+=20temperature:0=20falsy?= =?UTF-8?q?=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/modules/marketplace/routes.test.ts | 37 +++++++++++++++++++ .../src/modules/marketplace/routes.ts | 8 ++-- services/cowork-service/src/server.ts | 4 +- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/services/cowork-service/src/modules/marketplace/routes.test.ts b/services/cowork-service/src/modules/marketplace/routes.test.ts index ef2609fa..a1578af4 100644 --- a/services/cowork-service/src/modules/marketplace/routes.test.ts +++ b/services/cowork-service/src/modules/marketplace/routes.test.ts @@ -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; + 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; + expect(fetchHeaders['authorization']).toBeUndefined(); + }); + // ── Error Handling ── it('returns 502 when platform-service is unreachable', async () => { diff --git a/services/cowork-service/src/modules/marketplace/routes.ts b/services/cowork-service/src/modules/marketplace/routes.ts index c9f3d9e9..49d1f268 100644 --- a/services/cowork-service/src/modules/marketplace/routes.ts +++ b/services/cowork-service/src/modules/marketplace/routes.ts @@ -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 { +function proxyHeaders(req: { id: string; headers: Record; jwtPayload?: { sub: string } }): Record { const headers: Record = { '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; } diff --git a/services/cowork-service/src/server.ts b/services/cowork-service/src/server.ts index b409102a..1bc236cd 100644 --- a/services/cowork-service/src/server.ts +++ b/services/cowork-service/src/server.ts @@ -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)