fix(cloud): RS-5 a Claude credential is always labelled "Claude subscription": never this computer's email or the Orca name; old labels read the same

This commit is contained in:
Neil
2026-11-13 02:10:24 -08:00
parent 4554d946a1
commit 83ccaa4a8b
7 changed files with 19 additions and 25 deletions
@@ -141,11 +141,16 @@ export async function agentCredentialsView(db: D1Database, env: Env, userId: str
const view = (agent: CredentialAgent) => {
const row = results.find((r) => r.agent === agent) ?? null
const state = credentialState(row, now)
return { state, ...(row?.label ? { label: row.label } : {}), ...(row?.expires_at ? { expiresAt: row.expires_at } : {}) }
// RS-5: credentials saved before the fix carry an email or the Orca name; Claude's always reads true.
const label = agent === 'claude' && row ? CLAUDE_CREDENTIAL_LABEL : row?.label
return { state, ...(label ? { label } : {}), ...(row?.expires_at ? { expiresAt: row.expires_at } : {}) }
}
return { claude: view('claude'), codex: view('codex'), providerEnabled: providerEnabled(env), providerState: providerStates(env) }
}
/** RS-5: what a stored Claude credential is called (the approved account itself can't be known). */
export const CLAUDE_CREDENTIAL_LABEL = 'Claude subscription'
// A Claude setup-token: `sk-ant-oat01-` then URL-safe characters. Shape-checked, never logged.
const CLAUDE_TOKEN = /^sk-ant-oat01-[A-Za-z0-9_-]{20,500}$/
export const PutClaudeSchema = z
@@ -173,7 +178,10 @@ export async function putClaudeCredential(db: D1Database, keyring: Keyring, env:
accountId: caller.accountId,
agent: 'claude',
value: token,
label: body.label?.trim() || caller.name || null,
// RS-5 (design-squad): a true label only. Neither this computer's Claude Code email nor the Orca
// name says which account approved the setup-token (it prints no email, and its inference-only
// scope can't read the profile), so a Claude credential is always "Claude subscription".
label: CLAUDE_CREDENTIAL_LABEL,
expiresAt: Math.min(body.expiresAt ?? now + CLAUDE_SETUP_TOKEN_TTL_MS, now + CLAUDE_SETUP_TOKEN_TTL_MS)
},
now
@@ -108,7 +108,8 @@ describe('Orca-account credentials (D11)', () => {
expect((await h.call('PUT', '/v1/agent-credentials/claude', { bearer: mia, body: { token: 'sk-ant-api03-not-a-setup-token' } })).body.reason).toBe('invalid_token')
const put = await h.call('PUT', '/v1/agent-credentials/claude', { bearer: mia, body: { token: SETUP_TOKEN, label: 'mia@example.com' } })
expect(put.status).toBe(200)
expect(put.body.claude).toMatchObject({ state: 'active', label: 'mia@example.com' })
// RS-5: never the email a client sends (it may be another account's): always "Claude subscription".
expect(put.body.claude).toMatchObject({ state: 'active', label: 'Claude subscription' })
expect(put.body.providerEnabled).toEqual({ claude: true, codex: false })
// Codex's flag was never turned on: `off`, which clients word differently from the kill switch.
expect(put.body.providerState).toEqual({ claude: 'on', codex: 'off' })
@@ -155,7 +155,6 @@ describe('service: the code from the strip, Settings or the chat UI', () => {
children.push(child)
return child
},
readClaudeEmail: () => null,
openExternal: vi.fn(async () => {}),
broadcast: (setups) => broadcasts.push(setups),
identity: () => 'p1:u1'
@@ -9,10 +9,6 @@ import {
import { withCloudBuildDefaults } from '../cloud-build-defaults'
import { getHostedSandboxConfig } from '../hosted-sandbox/hosted-sandbox-config'
import { orcaCloudSandboxIdFor } from '../hosted-sandbox/orca-cloud-sandbox-id'
import {
claudeStateFile,
readClaudeFolderLogin
} from '../claude-accounts/sign-in/claude-account-folder'
import { ensureActiveOrcaProfile } from '../orca-profiles/profile-index-store'
import { getOrcaProfileIndexGeneration } from '../orca-profiles/profile-index-generation'
import { createAgentCredentialsClient } from './agent-credentials-client'
@@ -61,14 +57,6 @@ function broadcast(setups: OrcaCloudAgentSetups): void {
}
}
function readClaudeEmail(): string | null {
try {
return readClaudeFolderLogin(claudeStateFile(process.env.CLAUDE_CONFIG_DIR))?.email ?? null
} catch {
return null
}
}
/** The signed-in Orca user, re-read only when the profile index changes (sign-in, sign-out, switch). */
function orcaIdentityReader(userDataPath: string): () => string | null {
let cached: { generation: number; identity: string | null } | null = null
@@ -110,7 +98,6 @@ function serviceFor(userDataPath: string): OrcaCloudAgentsService {
})
: null,
spawnSetupToken: spawnClaudeSetupTokenPty,
readClaudeEmail,
openExternal: (url) => shell.openExternal(url),
broadcast,
identity: orcaIdentityReader(userDataPath)
@@ -86,7 +86,6 @@ describe('orca cloud agents service', () => {
const service = createOrcaCloudAgentsService({
client,
spawnSetupToken: async () => child,
readClaudeEmail: () => 'a@acme.dev',
openExternal,
broadcast: (setups) => broadcasts.push(setups),
identity
@@ -102,7 +101,7 @@ describe('orca cloud agents service', () => {
}
}
it('captures the setup-token in main and stores it with the local account as label', async () => {
it('captures the setup-token in main and stores it with no label (RS-5: the server names it truly)', async () => {
const { service, child, broadcasts, client, openExternal } = setup()
const reply = service.start('claude')
await tick()
@@ -117,7 +116,8 @@ describe('orca cloud agents service', () => {
child.emit(`Your OAuth token (valid for 1 year):\n\n${TOKEN}\n\nStore this token`)
child.exit()
await tick()
expect(client.putClaude).toHaveBeenCalledWith(TOKEN, 'a@acme.dev')
// RS-5: never this computer's Claude Code email; it may belong to another account.
expect(client.putClaude).toHaveBeenCalledWith(TOKEN, undefined)
expect(service.setups().claude).toEqual({ phase: 'saved' })
expectNoToken(reply, broadcasts, service.setups(), await service.status())
})
@@ -211,7 +211,6 @@ describe('orca cloud agents service', () => {
const service = createOrcaCloudAgentsService({
client: null,
spawnSetupToken: async () => fakeChild(),
readClaudeEmail: () => null,
openExternal: vi.fn(),
broadcast: vi.fn()
})
@@ -29,8 +29,6 @@ import {
export type OrcaCloudAgentsServiceDeps = {
client: AgentCredentialsClient | null
spawnSetupToken: SpawnSetupToken
/** The Claude account signed in on this computer, used as the stored credential's label. */
readClaudeEmail: () => string | null
openExternal: (url: string) => Promise<void>
broadcast: (setups: OrcaCloudAgentSetups) => void
/**
@@ -161,7 +159,8 @@ export function createOrcaCloudAgentsService(deps: OrcaCloudAgentsServiceDeps) {
isSignInUrl: (url) => isProviderSignInUrl('claude', url),
onSetup: (setup) => update('claude', setup),
onToken: async (token) => {
await storeClaude(token, deps.readClaudeEmail(), owner)
// RS-5: no label: this computer's Claude Code email may be another account's; the server names it.
await storeClaude(token, null, owner)
},
onDone: () => {
if (claudeRun === run) {
@@ -60,9 +60,10 @@ function fixture(
})
}
// RS-5: a Claude credential is always labelled "Claude subscription" (the approved account can't be known).
const CLAUDE_ACTIVE: OrcaCloudAccountRow = {
kind: 'active',
label: 'a@acme.dev',
label: 'Claude subscription',
expiresAt: NOW + YEAR,
expiring: false,
source: 'sign-in',