Skip to content

Commit bdb38e6

Browse files
GiniGini
authored andcommitted
Reject direct first-party relay endpoints
1 parent f07e84c commit bdb38e6

5 files changed

Lines changed: 30 additions & 4 deletions

File tree

HANDOVER.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ The abstraction that enforces this: `server/runtime-adapter.ts` — the `Runtime
4343
| SSE streaming | `server/task-event-stream.ts` | Real |
4444
| Approval service | `server/wallet-approval-service.ts` | Real — wallet-gated approvals |
4545
| UI — cosmetic | `src/index.css`, `src/components/*` | Done — Claude-calibrated light mode, Inter font, cream palette |
46-
| Tests | `server/*.test.ts`, `src/components/*.test.ts`, `scripts/*.test.ts` | 252 tests passing |
46+
| Tests | `server/*.test.ts`, `src/components/*.test.ts`, `scripts/*.test.ts` | 256 tests passing |
4747
| Container | `Dockerfile`, `docker-compose.yml` | Local hardened image verified; SQLite volume only until Postgres/auth slices land |
4848

4949
### What is critically broken
@@ -74,7 +74,7 @@ Do not configure a direct Anthropic API key as a substitute for the relay. Local
7474
```bash
7575
npm run check
7676
# = oxlint src server scripts
77-
# + vitest run (252 tests at this handover update)
77+
# + vitest run (256 tests at this handover update)
7878
# + tsc -b
7979
# + tsc -p tsconfig.server.json
8080
# + vite build

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
>
99
> **Current state**: The local-first foundation is substantially implemented: backend-offline recovery, truthful simulation labelling, durable SSE replay/reconnect, LiteLLM-only routing, provider-neutral runtime lifecycle, runtime health/routing, task workspaces, durable guidance queueing, assistant-ui conversation rendering, owner-scoped local auth, bounded MCP health/facade controls, and the initial skill marketplace boundary are in place. Remaining release blockers are live provider acceptance, Postgres/runtime deployment, sandbox attestation, production MCP secret brokering/external health attestation, document/provider/browser evidence, and the remaining professional-UI gates. See `HANDOVER.md` and `plan/00-gap-analysis.md`.
1010
>
11-
> **Release gate**: `npm run check` must stay green (oxlint + 252 vitest tests + tsc build + e2e harness typecheck) after every task.
11+
> **Release gate**: `npm run check` must stay green (oxlint + 256 vitest tests + tsc build + e2e harness typecheck) after every task.
1212
1313
> **Current handover policy**: all model traffic must traverse the protected LiteLLM boundary. Direct first-party Anthropic API traffic is prohibited, not a fallback. The Claude SDK configuration now fails closed unless the server-controlled relay is configured; Codex/AgentCore remain blocked until their adapters also use the same boundary.
1414

docs/IMPLEMENTATION-LOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Implementation log
22

3+
## 2026-07-17 — fail closed on known first-party model endpoints
4+
5+
- Expanded the shared `isLiteLlmRelayUrl` guard used by the Claude SDK and ONEComputer worker to reject known Anthropic, OpenAI, Bedrock, Gemini, Groq, Mistral, Cohere, xAI, and DeepSeek first-party hosts when they are mislabeled as LiteLLM relays.
6+
- Added regression coverage for OpenAI, Bedrock, Gemini, and Groq endpoints. A relay must remain an operator-controlled HTTP(S) endpoint without embedded credentials; the server never silently falls back to a first-party provider.
7+
- Focused provider tests, lint, build, and the full test suite pass (51 files / 256 tests).
8+
39
## 2026-07-17 — enforce the hardened container contract in CI
410

511
- Added a separate GitHub Actions container job that builds the multi-stage image, starts it with a read-only root filesystem, no-new-privileges, dropped capabilities, and an ephemeral writable data mount, then verifies `/api/health` and UID 10001.

server/claude-provider-config.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,13 @@ describe('Claude provider configuration', () => {
3636
expect(isLiteLlmRelayUrl('https://api.anthropic.com')).toBe(false)
3737
expect(isLiteLlmRelayUrl('https://relay.internal.example/v1')).toBe(true)
3838
})
39+
40+
it.each([
41+
'https://api.openai.com/v1',
42+
'https://bedrock-runtime.us-east-1.amazonaws.com',
43+
'https://generativelanguage.googleapis.com/v1beta',
44+
'https://api.groq.com/openai/v1',
45+
])('rejects known first-party provider relay URL %s', (url) => {
46+
expect(isLiteLlmRelayUrl(url)).toBe(false)
47+
})
3948
})

server/claude-provider-config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ export type ClaudeProviderConfig = {
66
}
77

88
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, '')
9+
const firstPartyProviderHost = (hostname: string) => hostname === 'api.anthropic.com'
10+
|| hostname.endsWith('.anthropic.com')
11+
|| hostname === 'api.openai.com'
12+
|| hostname.endsWith('.openai.com')
13+
|| hostname === 'generativelanguage.googleapis.com'
14+
|| hostname === 'api.groq.com'
15+
|| hostname === 'api.mistral.ai'
16+
|| hostname === 'api.cohere.com'
17+
|| hostname === 'api.x.ai'
18+
|| hostname === 'api.deepseek.com'
19+
|| /^bedrock(?:-runtime)?\.[a-z0-9-]+\.amazonaws\.com$/.test(hostname)
920

1021
/**
1122
* A relay variable is not permission to call a provider directly. Keep the
@@ -17,7 +28,7 @@ export const isLiteLlmRelayUrl = (value: string): boolean => {
1728
const url = new URL(value)
1829
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return false
1930
const hostname = url.hostname.toLowerCase()
20-
return hostname !== 'api.anthropic.com' && !hostname.endsWith('.anthropic.com')
31+
return !firstPartyProviderHost(hostname)
2132
} catch {
2233
return false
2334
}

0 commit comments

Comments
 (0)