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:
saravanakumardb1 2026-04-03 01:32:34 -07:00
parent bba26f2d5f
commit 2d70eeeeb0
3 changed files with 43 additions and 6 deletions

View File

@ -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 () => {

View File

@ -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;
}

View File

@ -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)