Skip to content

fix: api-proxy auth chain — trim keys, align placeholder format, add diagnostics#1528

Merged
lpcox merged 2 commits intomainfrom
copilot/fix-api-proxy-auth-chain
Mar 31, 2026
Merged

fix: api-proxy auth chain — trim keys, align placeholder format, add diagnostics#1528
lpcox merged 2 commits intomainfrom
copilot/fix-api-proxy-auth-chain

Conversation

@lpcox
Copy link
Copy Markdown
Collaborator

@lpcox lpcox commented Mar 31, 2026

Summary

Fixes #1527 — Anthropic API calls rejected despite correct routing through the api-proxy auth chain.

Changes

1. Trim API keys in api-proxy (containers/api-proxy/server.js)

CI secrets and docker-compose YAML may include trailing whitespace or newlines. When the api-proxy reads ANTHROPIC_API_KEY from its environment and injects it into the x-api-key header without trimming, a trailing newline corrupts the HTTP header (the newline terminates it prematurely). This produces silent auth failures where the upstream returns an error that Claude Code reports as error: "unknown", error_status: null.

// Before: raw env var, may contain trailing whitespace
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;

// After: trimmed, undefined if empty
const ANTHROPIC_API_KEY = (process.env.ANTHROPIC_API_KEY || '').trim() || undefined;

2. Align ANTHROPIC_AUTH_TOKEN placeholder with sk-ant- key format (src/docker-manager.ts)

Claude Code validates API key format before making calls. The old placeholder placeholder-token-for-credential-isolation lacks the sk-ant- prefix that Claude Code expects. Now uses the same value as apiKeyHelper output: sk-ant-placeholder-key-for-credential-isolation.

This also eliminates the inconsistency where the agent had two different placeholder values:

  • ANTHROPIC_AUTH_TOKEN=placeholder-token-for-credential-isolation (env var) ← fixed
  • apiKeyHelpersk-ant-placeholder-key-for-credential-isolation (script output)

3. Add auth header injection + upstream error logging (containers/api-proxy/server.js)

  • auth_inject debug log: Confirms the real API key is being injected (shows key length and redacted preview, not the actual key)
  • upstream_auth_error warning: Prominently logs 401/403 responses from upstream APIs with a diagnostic message

These logs make it possible to diagnose credential-isolation failures without access to the actual API keys.

Testing

  • ✅ 275 unit tests pass (docker-manager.test.ts, 3 pre-existing sudo failures unrelated)
  • ✅ 134 api-proxy tests pass (containers/api-proxy/)
  • ✅ Lint passes (0 errors)
  • ✅ Integration test expectations updated for new placeholder value

References

…diagnostics

Three fixes for the Anthropic API proxy auth chain failure (#1527):

1. Trim API keys read from env vars in api-proxy (server.js)
   CI secrets and docker-compose YAML may include trailing whitespace
   or newlines. Untrimmed keys produce malformed HTTP headers (the
   newline terminates the header prematurely), causing silent auth
   failures with error_status: null.

2. Align ANTHROPIC_AUTH_TOKEN placeholder with sk-ant- key format
   Claude Code validates key format before making API calls. The old
   placeholder 'placeholder-token-for-credential-isolation' lacks the
   sk-ant- prefix and may fail format validation. Now uses the same
   value as apiKeyHelper: 'sk-ant-placeholder-key-for-credential-isolation'.

3. Add auth header injection + upstream error logging in api-proxy
   - 'auth_inject' debug log confirms key injection (length, preview)
   - 'upstream_auth_error' warning on 401/403 from upstream API

Closes #1527

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lpcox lpcox requested a review from Mossaka as a code owner March 31, 2026 21:08
Copilot AI review requested due to automatic review settings March 31, 2026 21:08
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 82.67% 82.77% 📈 +0.10%
Statements 82.34% 82.43% 📈 +0.09%
Functions 81.22% 81.22% ➡️ +0.00%
Branches 75.94% 76.00% 📈 +0.06%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 85.8% → 86.2% (+0.41%) 85.3% → 85.7% (+0.40%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Anthropic authentication failures in the api-proxy credential-isolation chain by preventing malformed auth headers, aligning placeholder token format with Claude Code validation expectations, and adding targeted diagnostics for auth injection and upstream auth errors.

Changes:

  • Trim whitespace/newlines from API key env vars in api-proxy before injecting headers.
  • Update ANTHROPIC_AUTH_TOKEN placeholder to an sk-ant--prefixed value and adjust related tests.
  • Add structured logs for auth header injection and explicit warnings on upstream 401/403 responses.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
containers/api-proxy/server.js Trim env-provided credentials; add auth injection debug logging and upstream 401/403 diagnostics.
src/docker-manager.ts Change Claude Code placeholder token to an sk-ant--prefixed value for format validation compatibility.
src/docker-manager.test.ts Update unit test expectations for the new Anthropic placeholder token value.
tests/integration/api-proxy.test.ts Update integration test expectation for the new Anthropic placeholder token value.
Comments suppressed due to low confidence (2)

containers/api-proxy/server.js:342

  • Logging key_preview still reveals parts of the injected credential (first 8 + last 4 chars). Even partial token logging can be considered sensitive and can leak identifying material. Prefer logging only non-sensitive metadata (e.g., key length, header type) or a one-way hash/fingerprint.
      const keyPreview = injectedKey.length > 8
        ? `${injectedKey.substring(0, 8)}...${injectedKey.substring(injectedKey.length - 4)}`
        : '(short)';

containers/api-proxy/server.js:349

  • logRequest is documented as taking level "info" | "warn" | "error" (see containers/api-proxy/logging.js), but this introduces a new "debug" level. Either update the logging contract/tests to explicitly support "debug", or log this event at an existing level to avoid surprising downstream log consumers.
      logRequest('debug', 'auth_inject', {
        request_id: requestId,
        provider,
        key_length: injectedKey.length,
        key_preview: keyPreview,
        has_anthropic_version: !!headers['anthropic-version'],
      });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// Real authentication happens via ANTHROPIC_BASE_URL pointing to api-proxy
environment.ANTHROPIC_AUTH_TOKEN = 'placeholder-token-for-credential-isolation';
// Use sk-ant- prefix so Claude Code's key-format validation passes
environment.ANTHROPIC_AUTH_TOKEN = 'sk-ant-placeholder-key-for-credential-isolation';
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing ANTHROPIC_AUTH_TOKEN placeholder here will cause the agent entrypoint pre-flight script (containers/agent/api-proxy-health-check.sh) to exit 1 because it still hard-codes the previous value ('placeholder-token-for-credential-isolation'). Update the health check to accept the new placeholder (or relax the check) so api-proxy-enabled runs don’t fail before the user command executes.

Suggested change
environment.ANTHROPIC_AUTH_TOKEN = 'sk-ant-placeholder-key-for-credential-isolation';
environment.ANTHROPIC_AUTH_TOKEN = 'sk-ant-placeholder-token-for-credential-isolation';

Copilot uses AI. Check for mistakes.
Object.assign(headers, injectHeaders);

// Log auth header injection for debugging credential-isolation issues
const injectedKey = injectHeaders['x-api-key'] || injectHeaders['authorization'];
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

injectHeaders uses 'Authorization' (capital A) for OpenAI/Copilot, but this diagnostic checks only injectHeaders['authorization'] (lowercase). As a result, auth_inject logs won’t trigger for those providers. Consider normalizing header keys (e.g., lowercasing injectHeaders keys) or checking both cases so the diagnostic works consistently.

This issue also appears in the following locations of the same file:

  • line 340
  • line 343
Suggested change
const injectedKey = injectHeaders['x-api-key'] || injectHeaders['authorization'];
const injectedKey = injectHeaders['x-api-key']
|| injectHeaders['authorization']
|| injectHeaders['Authorization'];

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

🤖 Smoke test results for @lpcox's PR:

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot for issue #1528

@github-actions
Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3 ❌ NO
Node.js v24.14.0 v20.20.1 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot for issue #1528

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

The api-proxy-health-check.sh validates that ANTHROPIC_AUTH_TOKEN
contains a placeholder value. Update the expected value to match
the new sk-ant- prefixed placeholder set in docker-manager.ts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results — Copilot Engine

Test Status
GitHub MCP (last 2 merged PRs) #1508 "fix: copy get-claude-key.sh to chroot-accessible path", #1498 "[WIP] Fix failing GitHub Actions workflow Audit Main Package"
Playwright (github.com title) ✅ Title contains "GitHub"
File write /tmp/gh-aw/agent/smoke-test-copilot-23823419275.txt created
Bash verify cat confirmed content

Overall: PASS

PR author: @lpcox. Assignees on recent PRs: @lpcox, @Copilot.

📰 BREAKING: Report filed by Smoke Copilot for issue #1528

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results

✅ GitHub MCP: "fix: copy get-claude-key.sh to chroot-accessible path", "feat: add volume mount for ~/.copilot/session-state to persist events.jsonl"
✅ Playwright: GitHub page title verified
✅ File write: /tmp/gh-aw/agent/smoke-test-claude-23823419317.txt created
✅ Bash: File contents confirmed

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude for issue #1528

@github-actions
Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3
Node.js v24.14.0 v20.20.1
Go go1.22.12 go1.22.12

Result: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot for issue #1528

@github-actions github-actions bot mentioned this pull request Mar 31, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx All passed ✅ PASS
Node.js execa All passed ✅ PASS
Node.js p-limit All passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #1528 ·

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@lpcox lpcox merged commit 3c695f7 into main Mar 31, 2026
58 of 59 checks passed
@lpcox lpcox deleted the copilot/fix-api-proxy-auth-chain branch March 31, 2026 23:23
@github-actions
Copy link
Copy Markdown
Contributor

Smoke test results (workflow run 23824467737)
PR titles: "fix: api-proxy auth chain — trim keys, align placeholder format, add diagnostics"; "fix: copy get-claude-key.sh to chroot-accessible path"

  1. GitHub MCP last 2 merged PRs: ✅
  2. safeinputs-gh PR query: ❌
  3. Playwright github.com title check: ❌
  4. Tavily search: ❌
  5. File write test: ✅
  6. Bash cat verification: ✅
  7. Discussion query + mystical comment: ❌
  8. npm ci && npm run build: ✅
    Overall status: FAIL

🔮 The oracle has spoken through Smoke Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

api-proxy: Anthropic API calls rejected despite correct routing (auth chain failure)

2 participants