diff --git a/hub/package-lock.json b/hub/package-lock.json index 27caf6d..e09330d 100644 --- a/hub/package-lock.json +++ b/hub/package-lock.json @@ -1,12 +1,12 @@ { "name": "@paradigm/hub", - "version": "0.0.16", + "version": "0.0.17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@paradigm/hub", - "version": "0.0.16", + "version": "0.0.17", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.3.202", "@fastify/cookie": "^11.0.2", diff --git a/hub/package.json b/hub/package.json index 4a5ca67..bb661ba 100644 --- a/hub/package.json +++ b/hub/package.json @@ -1,6 +1,6 @@ { "name": "@paradigm/hub", - "version": "0.0.16", + "version": "0.0.17", "private": true, "type": "module", "engines": { diff --git a/hub/src/connections/providerProxy.ts b/hub/src/connections/providerProxy.ts index b76a2fb..ab89e8f 100644 --- a/hub/src/connections/providerProxy.ts +++ b/hub/src/connections/providerProxy.ts @@ -41,6 +41,11 @@ export interface ProviderProxyDiagnostic { | "provider_proxy_redirect_refused" | "provider_proxy_upstream_failed"; readonly category: NetworkFailureCategory | "authorization" | "request_limit" | "redirect"; + readonly authShape?: { + readonly bearerLength: number; + readonly apiKeyLength: number; + readonly expectedLength: number; + }; } export interface ProviderProxyOptions { @@ -80,7 +85,7 @@ export async function openProviderProxyLease( return { sdkEnv: { ANTHROPIC_BASE_URL: `http://127.0.0.1:${address.port}`, - ANTHROPIC_AUTH_TOKEN: "", + ANTHROPIC_AUTH_TOKEN: capability, ANTHROPIC_API_KEY: capability, }, sensitiveValues: [capability], @@ -103,7 +108,17 @@ async function forwardProviderRequest( options: ProviderProxyOptions, ): Promise { if (!authorized(request.headers.authorization, header(request.headers["x-api-key"]), capability)) { - options.onDiagnostic?.({ code: "provider_proxy_unauthorized", category: "authorization" }); + options.onDiagnostic?.({ + code: "provider_proxy_unauthorized", + category: "authorization", + authShape: { + bearerLength: request.headers.authorization?.startsWith("Bearer ") + ? request.headers.authorization.length - "Bearer ".length + : 0, + apiKeyLength: header(request.headers["x-api-key"])?.length ?? 0, + expectedLength: capability.length, + }, + }); response.writeHead(401, { "content-type": "text/plain; charset=utf-8" }); response.end("unauthorized"); return; diff --git a/hub/src/hub.ts b/hub/src/hub.ts index bc24897..af4eff0 100644 --- a/hub/src/hub.ts +++ b/hub/src/hub.ts @@ -73,6 +73,7 @@ export async function startHub(): Promise { providerId: context.providerId, errorCode: diagnostic.code, failureCategory: diagnostic.category, + authShape: diagnostic.authShape, }, "provider proxy diagnostic"); }, }), diff --git a/hub/test/unit/provider-proxy.test.ts b/hub/test/unit/provider-proxy.test.ts index 3d2590b..bf4e6b7 100644 --- a/hub/test/unit/provider-proxy.test.ts +++ b/hub/test/unit/provider-proxy.test.ts @@ -52,9 +52,10 @@ describe("run-scoped provider proxy", () => { expect(serializedAgentEnv).not.toContain(String(address.port)); expect(lease.sdkEnv).toMatchObject({ ANTHROPIC_BASE_URL: expect.stringMatching(/^http:\/\/127\.0\.0\.1:\d+$/), - ANTHROPIC_AUTH_TOKEN: "", + ANTHROPIC_AUTH_TOKEN: expect.any(String), ANTHROPIC_API_KEY: expect.any(String), }); + expect(lease.sdkEnv.ANTHROPIC_AUTH_TOKEN).toBe(lease.sdkEnv.ANTHROPIC_API_KEY); const response = await fetch(`${lease.sdkEnv.ANTHROPIC_BASE_URL}/v1/messages`, { method: "POST", @@ -104,7 +105,11 @@ describe("run-scoped provider proxy", () => { expect(response.status).toBe(401); expect(upstreamRequests).toBe(0); - expect(diagnostics).toEqual([{ code: "provider_proxy_unauthorized", category: "authorization" }]); + expect(diagnostics).toEqual([{ + code: "provider_proxy_unauthorized", + category: "authorization", + authShape: { bearerLength: 0, apiKeyLength: 0, expectedLength: 43 }, + }]); }); it("accepts the run capability in the Anthropic x-api-key header", async () => {