fix(zcode): isolate read-only account discovery and quota ownership

Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: GuanBear <guanbear@users.noreply.github.com>
This commit is contained in:
Neil
2026-10-10 16:04:08 -07:00
committed by Neil
co-authored by Luchong GuanBear
parent 3f7943c411
commit 87da8432cd
26 changed files with 1425 additions and 123 deletions
@@ -1,3 +1,7 @@
import { cpSync, mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { readZcodeUsageCredentials } from './zcode-usage-credentials'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { ProviderRateLimits } from '../../shared/rate-limit-types'
import { RateLimitService } from './service'
@@ -186,4 +190,37 @@ describe('RateLimitService zcode plan credentials', () => {
expect(fetchZcodeRateLimits).toHaveBeenCalledTimes(2)
expect(state.zcode?.session?.usedPercent).toBe(10)
})
it('rejects an old CLI account result after sibling-provider waits', async () => {
const home = mkdtempSync(join(tmpdir(), 'orca-zcode-service-v2-'))
const fixtures = join(import.meta.dirname, '..', 'zcode', 'fixtures', 'v2-upstream')
vi.stubEnv('ZCODE_DATA_BASE_DIR', home)
vi.stubEnv('ZCODE_CREDENTIAL_SECRET', 'synthetic-secret')
try {
cpSync(join(fixtures, 'zai'), join(home, '.zcode', 'v2'), { recursive: true })
const selected = readZcodeUsageCredentials()
if (selected.status !== 'ok') {
throw new Error('Synthetic fixture could not be read')
}
const claude = deferred<ProviderRateLimits>()
vi.mocked(fetchClaudeRateLimits).mockImplementationOnce(() => claude.promise)
vi.mocked(fetchZcodeRateLimits).mockResolvedValueOnce({
...okProvider('zcode', 44, Date.now()),
usageMetadata: {
authProvenance: selected.credentials.authProvenance,
credentialSource: selected.source
}
})
const service = new RateLimitService()
const refreshing = service.refresh()
await vi.waitFor(() => expect(fetchZcodeRateLimits).toHaveBeenCalled())
cpSync(join(fixtures, 'zai-other'), join(home, '.zcode', 'v2'), { recursive: true })
claude.resolve(okProvider('claude', 7))
await refreshing
expect(service.getState().zcode?.status).toBe('unavailable')
expect(service.getState().zcode?.session).toBeNull()
} finally {
vi.unstubAllEnvs()
rmSync(home, { recursive: true, force: true })
}
})
})
@@ -1,4 +1,5 @@
import { RateLimitServiceFullCyclePreparation } from './service-full-cycle-preparation'
import { isZcodeUsageCredentialCurrent } from '../zcode-usage-credentials'
import { settleSiblingProviderResult } from './service-sibling-provider-result'
import type { ProviderRateLimits } from './service-types'
@@ -202,7 +203,17 @@ export abstract class RateLimitServiceFullCycleApplication extends RateLimitServ
}
const grok = settleSiblingProviderResult('grok', grokSettled)
const cursor = settleSiblingProviderResult('cursor', cursorSettled)
const zcode = settleSiblingProviderResult('zcode', zcodeSettled)
const settledZcode = settleSiblingProviderResult('zcode', zcodeSettled)
const zcode = isZcodeUsageCredentialCurrent(settledZcode)
? settledZcode
: {
...settledZcode,
status: 'unavailable' as const,
session: null,
weekly: null,
monthly: null,
error: 'ZCode Coding Plan account changed during refresh'
}
const shouldApplyZcode = zcodeGeneration === this.zcodeFetchGeneration
const antigravity = settleSiblingProviderResult('antigravity', antigravitySettled)
// Why: the stale policy keeps a recent snapshot through a failed refresh, but
@@ -0,0 +1,142 @@
import { createHmac, randomBytes } from 'node:crypto'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import {
readZcodeV2PlanCredential,
localZcodeCredentialHost,
type ZcodeCredentialHost
} from '../zcode/zcode-v2-plan-credentials'
import type { ProviderRateLimits } from '../../shared/rate-limit-types'
const SUPPORTED_HOSTS = new Set(['api.z.ai', 'open.bigmodel.cn', 'dev.bigmodel.cn'])
const CREDENTIAL_IDENTITY_KEY = randomBytes(32)
export const ZCODE_PLAN_CREDENTIAL_SOURCE = 'orca-plan'
export type ZcodePlanCredential = { apiKey: string; baseUrl: string }
export type ZcodeUsageCredentials = { apiKey: string; quotaUrl: string; authProvenance: string }
export type ZcodeUsageCredentialOptions = {
configPath?: string
credentialHost?: ZcodeCredentialHost
}
export type ZcodeUsageCredentialRead =
| { status: 'unavailable' | 'error' }
| { status: 'ok'; credentials: ZcodeUsageCredentials; source: string }
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function readRecord(value: unknown): Record<string, unknown> | null {
return isRecord(value) ? value : null
}
function readMainProvider(model: unknown): string | null {
const name = typeof model === 'string' ? model : readRecord(model)?.main
if (typeof name !== 'string') {
return null
}
const delimiter = name.indexOf('/')
return delimiter > 0 && delimiter < name.length - 1 ? name.slice(0, delimiter) : null
}
function readCredentials(configPath: string): ZcodeUsageCredentials | null {
let config: Record<string, unknown> | null
try {
config = readRecord(JSON.parse(readFileSync(configPath, 'utf8')))
} catch {
return null
}
if (!config) {
return null
}
// A quota from another configured account must never appear as the selected model's quota.
const mainProvider = readMainProvider(config.model)
if (!mainProvider) {
return null
}
const options = readRecord(readRecord(readRecord(config.provider)?.[mainProvider])?.options)
const apiKey = options?.apiKey
const baseURL = options?.baseURL
if (
typeof apiKey !== 'string' ||
!apiKey.trim() ||
/[\r\n]/.test(apiKey) ||
typeof baseURL !== 'string'
) {
return null
}
return resolveUsageCredentials(apiKey, baseURL, mainProvider)
}
export function resolveUsageCredentials(
key: string,
baseUrl: string,
identity: string
): ZcodeUsageCredentials | null {
const apiKey = key.trim()
if (!apiKey || /[\r\n]/.test(apiKey)) {
return null
}
try {
const parsed = new URL(baseUrl)
if (
parsed.protocol !== 'https:' ||
!SUPPORTED_HOSTS.has(parsed.hostname) ||
(parsed.port !== '' && parsed.port !== '443')
) {
return null
}
return {
apiKey,
quotaUrl: `${parsed.origin}/api/monitor/usage/quota/limit`,
authProvenance: createHmac('sha256', CREDENTIAL_IDENTITY_KEY)
.update(JSON.stringify([identity, parsed.origin, apiKey]))
.digest('hex')
}
} catch {
return null
}
}
export function readZcodeUsageCredentials(
options: ZcodeUsageCredentialOptions = {}
): ZcodeUsageCredentialRead {
const host = options.credentialHost ?? localZcodeCredentialHost()
const configPath = options.configPath ?? join(host.home, '.zcode', 'cli', 'config.json')
// An explicit legacy fixture path never probes the developer's credential store.
const v2 =
options.configPath && !options.credentialHost
? { status: 'absent' as const }
: readZcodeV2PlanCredential(host)
if (v2.status === 'error') {
return { status: 'error' }
}
if (v2.status === 'unavailable') {
return { status: 'unavailable' }
}
const credentials =
v2.status === 'ok'
? resolveUsageCredentials(v2.apiKey, v2.baseUrl, v2.identity)
: readCredentials(configPath)
return credentials
? { status: 'ok', credentials, source: v2.status === 'ok' ? v2.configPath : configPath }
: { status: 'unavailable' }
}
export function hasZcodeCliPlanCredentials(configPath?: string): boolean {
return readZcodeUsageCredentials({ configPath }).status === 'ok'
}
export function isZcodeUsageCredentialCurrent(usage: ProviderRateLimits): boolean {
if (
!usage.usageMetadata?.authProvenance ||
!usage.usageMetadata.credentialSource ||
usage.usageMetadata.credentialSource === ZCODE_PLAN_CREDENTIAL_SOURCE
) {
return true
}
const current = readZcodeUsageCredentials()
return (
current.status === 'ok' &&
current.credentials.authProvenance === usage.usageMetadata.authProvenance
)
}
+43 -111
View File
@@ -1,13 +1,20 @@
import { createHmac, randomBytes } from 'node:crypto'
import { readFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { join } from 'node:path'
import {
readZcodeUsageCredentials,
resolveUsageCredentials,
ZCODE_PLAN_CREDENTIAL_SOURCE,
type ZcodePlanCredential,
type ZcodeUsageCredentials,
type ZcodeUsageCredentialOptions
} from './zcode-usage-credentials'
export {
hasZcodeCliPlanCredentials,
ZCODE_PLAN_CREDENTIAL_SOURCE,
type ZcodePlanCredential
} from './zcode-usage-credentials'
import { cancelUnreadResponseBody } from '../lib/unread-response-body'
import type { ProviderRateLimits, RateLimitWindow } from '../../shared/rate-limit-types'
const API_TIMEOUT_MS = 15_000
const SUPPORTED_HOSTS = new Set(['api.z.ai', 'open.bigmodel.cn', 'dev.bigmodel.cn'])
const CREDENTIAL_IDENTITY_KEY = randomBytes(32)
type QuotaLimit = {
type?: unknown
@@ -20,20 +27,6 @@ type QuotaLimit = {
nextResetTime?: unknown
}
type ZcodeUsageCredentials = {
apiKey: string
quotaUrl: string
authProvenance: string
}
/** A GLM Coding Plan key saved through Orca's AI Provider Accounts; takes priority over the ZCode CLI config. */
export type ZcodePlanCredential = {
apiKey: string
baseUrl: string
}
export const ZCODE_PLAN_CREDENTIAL_SOURCE = 'orca-plan'
// Why readers and not casts: both JSON sources are outside our control — a user-edited
// config file and a remote response — so their shape is a guess until something checks it.
function isRecord(value: unknown): value is Record<string, unknown> {
@@ -44,15 +37,6 @@ function readRecord(value: unknown): Record<string, unknown> | null {
return isRecord(value) ? value : null
}
function readMainProvider(model: unknown): string | null {
const name = typeof model === 'string' ? model : readRecord(model)?.main
if (typeof name !== 'string') {
return null
}
const delimiter = name.indexOf('/')
return delimiter > 0 && delimiter < name.length - 1 ? name.slice(0, delimiter) : null
}
function unavailable(error: string): ProviderRateLimits {
return {
provider: 'zcode',
@@ -87,72 +71,6 @@ function redactCredential(error: string, apiKey: string): string {
return error.replaceAll(apiKey, '[redacted]')
}
function readCredentials(configPath: string): ZcodeUsageCredentials | null {
let config: Record<string, unknown> | null
try {
config = readRecord(JSON.parse(readFileSync(configPath, 'utf8')))
} catch {
return null
}
if (!config) {
return null
}
// A quota from another configured account must never appear as the selected model's quota.
const mainProvider = readMainProvider(config.model)
if (!mainProvider) {
return null
}
const options = readRecord(readRecord(readRecord(config.provider)?.[mainProvider])?.options)
const apiKey = options?.apiKey
const baseURL = options?.baseURL
if (
typeof apiKey !== 'string' ||
!apiKey.trim() ||
/[\r\n]/.test(apiKey) ||
typeof baseURL !== 'string'
) {
return null
}
return resolveUsageCredentials(apiKey, baseURL, mainProvider)
}
function resolveUsageCredentials(
key: string,
baseUrl: string,
identity: string
): ZcodeUsageCredentials | null {
const apiKey = key.trim()
if (!apiKey || /[\r\n]/.test(apiKey)) {
return null
}
try {
const parsed = new URL(baseUrl)
if (
parsed.protocol !== 'https:' ||
!SUPPORTED_HOSTS.has(parsed.hostname) ||
(parsed.port !== '' && parsed.port !== '443')
) {
return null
}
return {
apiKey,
quotaUrl: `${parsed.origin}/api/monitor/usage/quota/limit`,
authProvenance: createHmac('sha256', CREDENTIAL_IDENTITY_KEY)
.update(JSON.stringify([identity, parsed.origin, apiKey]))
.digest('hex')
}
} catch {
return null
}
}
export function hasZcodeCliPlanCredentials(
configPath = join(homedir(), '.zcode', 'cli', 'config.json')
): boolean {
return readCredentials(configPath) !== null
}
function asNumber(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? value : null
}
@@ -210,35 +128,49 @@ function asWindow(limit: QuotaLimit | undefined): RateLimitWindow | null {
}
export async function fetchZcodeRateLimits(
options: {
configPath?: string
options: ZcodeUsageCredentialOptions & {
planCredential?: ZcodePlanCredential | null
signal?: AbortSignal
} = {}
): Promise<ProviderRateLimits> {
const configPath = options.configPath ?? join(homedir(), '.zcode', 'cli', 'config.json')
const planCredentials = options.planCredential
? resolveUsageCredentials(
options.planCredential.apiKey,
options.planCredential.baseUrl,
ZCODE_PLAN_CREDENTIAL_SOURCE
)
const plan = options.planCredential
const saved = plan
? resolveUsageCredentials(plan.apiKey, plan.baseUrl, ZCODE_PLAN_CREDENTIAL_SOURCE)
: null
if (!planCredentials && options.planCredential) {
// Why: a saved-but-unusable key must surface as its own error; silently
// falling back to the CLI config would show a different account's quota.
if (plan && !saved) {
return failed('The saved GLM Coding Plan API key is unusable', 'parse', '')
}
const credentials = planCredentials ?? readCredentials(configPath)
const cli = saved ? null : readZcodeUsageCredentials(options)
if (cli?.status === 'error') {
return failed('Could not read the selected ZCode CLI Coding Plan credential', 'parse', '')
}
const credentials = saved ?? (cli?.status === 'ok' ? cli.credentials : null)
if (!credentials) {
return unavailable('ZCode Coding Plan credentials are not configured')
}
const credentialSource = planCredentials ? ZCODE_PLAN_CREDENTIAL_SOURCE : configPath
const source = saved ? ZCODE_PLAN_CREDENTIAL_SOURCE : cli?.status === 'ok' ? cli.source : ''
const result = await fetchUsage(credentials, source, options.signal)
if (!saved) {
const current = readZcodeUsageCredentials(options)
if (
current.status !== 'ok' ||
current.credentials.authProvenance !== credentials.authProvenance
) {
return unavailable('ZCode Coding Plan account changed during refresh')
}
}
return { ...result, usageMetadata: { ...result.usageMetadata, credentialSource: source } }
}
async function fetchUsage(
credentials: ZcodeUsageCredentials,
credentialSource: string,
callerSignal?: AbortSignal
): Promise<ProviderRateLimits> {
let response: Response
try {
const signal = options.signal
? AbortSignal.any([options.signal, AbortSignal.timeout(API_TIMEOUT_MS)])
const signal = callerSignal
? AbortSignal.any([callerSignal, AbortSignal.timeout(API_TIMEOUT_MS)])
: AbortSignal.timeout(API_TIMEOUT_MS)
response = await fetch(credentials.quotaUrl, {
method: 'GET',
@@ -0,0 +1,4 @@
{
"account-provider:account:bigmodel-individual-coding-plan:identity": "enc:v1:Xa4djzkgGVuA9hgY.lE9U5VVGy6u5-yeidwVjNg.ryVJkols9xwgTv_GHgdb_WR0TGNVXofq9oDHoDXp",
"account-provider:coding-plan:account:bigmodel-individual-coding-plan:account:synthetic%20account%2F%40with%20spaces:api-key": "enc:v1:KncfBBD-_1mECFii.umdw6EsApWFPzaDSEJpvjQ.AkEUDOTLiKzFRhTvRScxXwEPoXPNAg"
}
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"config": {
"providerOrder": [],
"providerConfigRules": {
"providerRules": []
},
"modelConfigRules": {
"providerModelRules": [],
"manualProviderModelRules": []
},
"defaultModelSelection": {
"providerId": "account:bigmodel-individual-coding-plan",
"modelId": "GLM-5.3"
}
}
}
@@ -0,0 +1,4 @@
{
"account-provider:account:zai-individual-coding-plan:identity": "enc:v1:HfDnNz2oziizSVMW.YDBr9NKtzDAYF3nwluPqgA._K06iVA8mswh6ObM0TtvfG9ZMtvHhr0ytYc-Ky4d",
"account-provider:coding-plan:account:zai-individual-coding-plan:account:synthetic%20account%2F%40with%20spaces:api-key": "enc:v1:LIO_RzkH-jSL81wl.x3plNd8js7D5rDQDMF7_zg.T_1k-zSDyAeWgRyCO-erPSD28pcKyj4ABbAl"
}
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"config": {
"providerOrder": [],
"providerConfigRules": {
"providerRules": []
},
"modelConfigRules": {
"providerModelRules": [],
"manualProviderModelRules": []
},
"defaultModelSelection": {
"providerId": "account:zai-individual-coding-plan",
"modelId": "GLM-5.3"
}
}
}
@@ -0,0 +1,12 @@
{
"revision": "29628c9acdb81b703bbd4080c207a0e7ce5e276e",
"cliVersion": "0.16.9",
"writer": "SharedZCodeCredentialStore + NodePersonalProviderConfigRepository + NodeModelSelectionConfigRepository",
"secret": "synthetic-secret",
"fallbackHost": {
"platform": "linux",
"home": "/synthetic/home",
"username": "synthetic-user"
},
"identity": "synthetic account/@with spaces"
}
@@ -0,0 +1,4 @@
{
"account-provider:account:zai-individual-coding-plan:identity": "enc:v1:nydk9ZyxEN9uPpkb.HIVQq-Bb7vg50t_Oh31x6A.oTB8Ws2qrMfs8-eXZzP7khi8lCziuH5B",
"account-provider:coding-plan:account:zai-individual-coding-plan:account:synthetic%20account%2Fsecond:api-key": "enc:v1:kTCOzlXEAs738Y0_.G9qnJJ66F2jZIRBycu-0ag.zz0wpl0fQTqz9ilAR08Ufg_2EIIxIlg"
}
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"config": {
"providerOrder": [],
"providerConfigRules": {
"providerRules": []
},
"modelConfigRules": {
"providerModelRules": [],
"manualProviderModelRules": []
},
"defaultModelSelection": {
"providerId": "account:zai-individual-coding-plan",
"modelId": "GLM-5.3"
}
}
}
@@ -0,0 +1,4 @@
{
"account-provider:account:zai-individual-coding-plan:identity": "enc:v1:f7YgLlGR-NmwR8sb.OH3Pnq4j0CoNjKnjPei8Sw.cN-lfdrwUsEE4UlnS9xTFoFgwhQqyY7OQy64_TQ8",
"account-provider:coding-plan:account:zai-individual-coding-plan:account:synthetic%20account%2F%40with%20spaces:api-key": "enc:v1:BMaY1kv0yy5BA1Sp.YMe8YhN_Q8KVb6RI6zKR8Q.BwywWeSZjU6r01MsI81TZOo"
}
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"config": {
"providerOrder": [],
"providerConfigRules": {
"providerRules": []
},
"modelConfigRules": {
"providerModelRules": [],
"manualProviderModelRules": []
},
"defaultModelSelection": {
"providerId": "account:zai-individual-coding-plan",
"modelId": "GLM-5.3"
}
}
}
@@ -0,0 +1,124 @@
import * as fs from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { fetchZcodeRateLimits } from '../rate-limits/zcode-usage-fetcher'
import { readZcodeUsageCredentials } from '../rate-limits/zcode-usage-credentials'
import { readZcodeV2PlanCredential, type ZcodeCredentialHost } from './zcode-v2-plan-credentials'
vi.mock('node:fs', async (importOriginal) => ({
...(await importOriginal<typeof fs>())
}))
let home: string
let host: ZcodeCredentialHost
let configPath: string
let vaultPath: string
beforeEach(() => {
home = fs.mkdtempSync(join(tmpdir(), 'orca-zcode-raw-json-'))
fs.cpSync(
join(import.meta.dirname, 'fixtures', 'v2-upstream', 'bigmodel'),
join(home, '.zcode', 'v2'),
{
recursive: true
}
)
configPath = join(home, '.zcode', 'v2', 'provider_config.json')
vaultPath = join(home, '.zcode', 'v2', 'credentials.json')
host = {
home,
platform: 'linux',
username: 'synthetic-user',
env: { ZCODE_DATA_BASE_DIR: home, ZCODE_CREDENTIAL_SECRET: 'synthetic-secret' }
}
vi.stubGlobal(
'fetch',
vi.fn(
async () =>
new Response(
JSON.stringify({
success: true,
data: {
limits: [{ type: 'TOKENS_LIMIT', unit: 3, number: 5, percentage: 12 }]
}
})
)
)
)
})
afterEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
fs.rmSync(home, { recursive: true, force: true })
})
function installRawLeaf(group: string, rawLeaf: string): void {
const document = JSON.parse(fs.readFileSync(configPath, 'utf8'))
const selection = document.config.defaultModelSelection
const rule = {
providerId: selection.providerId,
...(group === 'providerRules' ? {} : { modelId: selection.modelId }),
config: JSON.parse(rawLeaf)
}
const rules =
group === 'providerRules'
? document.config.providerConfigRules
: document.config.modelConfigRules
rules[group] = [rule]
fs.writeFileSync(configPath, JSON.stringify(document))
}
const cases = ['providerRules', 'providerModelRules', 'manualProviderModelRules'].flatMap((group) =>
['__proto__', 'constructor', 'prototype'].map((key) => ({ group, key }))
)
describe('raw persisted personal rule keys', () => {
it.each(cases)(
'refuses $group with raw $key before vault access or quota',
async ({ group, key }) => {
installRawLeaf(group, `{"${key}":{"syntheticUnknownLeaf":true}}`)
const configBefore = fs.readFileSync(configPath)
const vaultBefore = fs.readFileSync(vaultPath)
const stat = vi.spyOn(fs, 'statSync')
const lstat = vi.spyOn(fs, 'lstatSync')
const open = vi.spyOn(fs, 'openSync')
const read = vi.spyOn(fs, 'readFileSync')
expect(readZcodeV2PlanCredential(host).status).toBe('unavailable')
expect(readZcodeUsageCredentials({ credentialHost: host }).status).toBe('unavailable')
expect((await fetchZcodeRateLimits({ credentialHost: host })).status).toBe('unavailable')
expect(fetch).not.toHaveBeenCalled()
for (const calls of [stat.mock.calls, lstat.mock.calls, open.mock.calls, read.mock.calls]) {
expect(calls.filter(([path]) => path === vaultPath)).toHaveLength(0)
}
expect(fs.readFileSync(configPath)).toEqual(configBefore)
expect(fs.readFileSync(vaultPath)).toEqual(vaultBefore)
const saved = await fetchZcodeRateLimits({
credentialHost: host,
planCredential: {
apiKey: 'synthetic-orca-key',
baseUrl: 'https://api.z.ai'
}
})
expect(saved.status).toBe('ok')
expect(saved.usageMetadata?.credentialSource).toBe('orca-plan')
expect(JSON.stringify(saved)).not.toContain('synthetic-orca-key')
}
)
it.each(['providerRules', 'providerModelRules'])('accepts a known empty %s config', (group) => {
installRawLeaf(group, '{}')
expect(readZcodeV2PlanCredential(host)).toMatchObject({
status: 'ok',
baseUrl: 'https://open.bigmodel.cn'
})
})
it.each(['null', '[]', '42'])('rejects a malformed raw config %s', (raw) => {
installRawLeaf('providerRules', raw)
expect(readZcodeV2PlanCredential(host).status).toBe('error')
})
})
@@ -0,0 +1,73 @@
import { z } from 'zod'
// Preserve raw own keys: Zod records erase __proto__ before emptiness checks.
const rawConfigSchema = z.custom<Record<string, unknown>>(
(value) => typeof value === 'object' && value !== null && !Array.isArray(value)
)
const modelRuleSchema = z
.object({
providerId: z.string().min(1),
modelId: z.string().min(1),
config: rawConfigSchema
})
.strict()
const providerRuleSchema = z
.object({
providerId: z.string().min(1),
templateId: z.string().min(1).nullable().optional(),
providerName: z.string().min(1).nullable().optional(),
enabled: z.boolean().optional(),
config: rawConfigSchema
})
.strict()
const selectionSchema = z
.object({
schemaVersion: z.literal(1),
config: z
.object({
providerOrder: z.array(z.string().min(1)).optional(),
providerConfigRules: z.object({ providerRules: z.array(providerRuleSchema) }).strict(),
modelConfigRules: z
.object({
providerModelRules: z.array(modelRuleSchema),
manualProviderModelRules: z.array(modelRuleSchema)
})
.strict(),
defaultModelSelection: z
.object({
providerId: z.string().trim().min(1),
modelId: z.string().trim().min(1),
options: z
.object({ reasoningLevel: z.string().trim().min(1).optional() })
.strict()
.optional()
})
.strict()
.optional()
})
.strict()
})
.strict()
// ZCode 29628c9 validates the whole overlay before accepting its selected account.
export function readSupportedZcodeV2Selection(input: unknown) {
const { config } = selectionSchema.parse(input)
const providerRules = config.providerConfigRules.providerRules
const providerIds = new Set(providerRules.map((rule) => rule.providerId))
if (providerIds.size !== providerRules.length) {
throw new Error('Invalid personal provider rules')
}
// Empty smart rules are validated; richer leaves and manual normalization are unsupported.
if (
providerRules.some((rule) => Object.keys(rule.config).length > 0) ||
config.modelConfigRules.providerModelRules.some(
(rule) => Object.keys(rule.config).length > 0
) ||
config.modelConfigRules.manualProviderModelRules.length > 0
) {
return undefined
}
return config.defaultModelSelection
}
@@ -0,0 +1,471 @@
import {
cpSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
writeFileSync
} from 'node:fs'
import { buildSync } from 'esbuild'
import { runProcess } from '@orca/process-host'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { readZcodeV2PlanCredential, type ZcodeCredentialHost } from './zcode-v2-plan-credentials'
import { readZcodeUsageCredentials } from '../rate-limits/zcode-usage-credentials'
import { fetchZcodeRateLimits } from '../rate-limits/zcode-usage-fetcher'
let dir: string
let host: ZcodeCredentialHost
const fixtureRoot = join(import.meta.dirname, 'fixtures', 'v2-upstream')
function install(name = 'zai') {
cpSync(join(fixtureRoot, name), join(dir, '.zcode', 'v2'), { recursive: true })
}
function legacy() {
mkdirSync(join(dir, '.zcode', 'cli'), { recursive: true })
writeFileSync(
join(dir, '.zcode', 'cli', 'config.json'),
JSON.stringify({
model: 'legacy/GLM',
provider: {
legacy: { options: { apiKey: 'synthetic-legacy-key', baseURL: 'https://open.bigmodel.cn' } }
}
})
)
}
function quota() {
return new Response(
JSON.stringify({
success: true,
data: { limits: [{ type: 'TOKENS_LIMIT', unit: 3, number: 5, percentage: 12 }] }
})
)
}
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'orca-zcode-v2-'))
host = {
home: dir,
platform: 'linux',
username: 'synthetic-user',
env: { ZCODE_DATA_BASE_DIR: dir, ZCODE_CREDENTIAL_SECRET: 'synthetic-secret' }
}
vi.stubGlobal(
'fetch',
vi.fn(async () => quota())
)
})
afterEach(() => {
vi.unstubAllGlobals()
rmSync(dir, { recursive: true, force: true })
})
describe('upstream-written ZCode v2 stores', () => {
it.each(['zai', 'bigmodel'])(
'reads selected %s plan, exact encoded identity key and site without writing',
async (name) => {
install(name)
const before = readFileSync(join(dir, '.zcode', 'v2', 'credentials.json'))
expect(readZcodeV2PlanCredential(host)).toMatchObject({
status: 'ok',
apiKey: `synthetic-${name}-key`,
baseUrl: name === 'zai' ? 'https://api.z.ai' : 'https://open.bigmodel.cn'
})
const result = await fetchZcodeRateLimits({ credentialHost: host })
expect(result.status).toBe('ok')
const call = vi.mocked(fetch).mock.calls[0]
expect(String(call[0])).toContain(name === 'zai' ? 'api.z.ai' : 'open.bigmodel.cn')
expect(new Headers(call[1]?.headers).get('Authorization')).toBe(`synthetic-${name}-key`)
expect(JSON.stringify(result)).not.toContain(`synthetic-${name}-key`)
expect(JSON.stringify(result)).not.toContain('synthetic account')
expect(readFileSync(join(dir, '.zcode', 'v2', 'credentials.json'))).toEqual(before)
expect(readdirSync(join(dir, '.zcode', 'v2')).sort()).toEqual([
'credentials.json',
'provider_config.json'
])
}
)
it('prefers migrated credentials over a stale legacy account', () => {
legacy()
install()
const result = readZcodeUsageCredentials({ credentialHost: host })
expect(result).toMatchObject({ status: 'ok', credentials: { apiKey: 'synthetic-zai-key' } })
})
it('keeps legacy support when no personal v2 file exists', () => {
legacy()
expect(readZcodeUsageCredentials({ credentialHost: host })).toMatchObject({
status: 'ok',
credentials: { apiKey: 'synthetic-legacy-key' }
})
})
it.each([
'unsupported',
'missing-selection',
'future-schema',
'invalid-json',
'bad-ciphertext',
'missing-key'
])('never substitutes legacy credentials for %s v2 state', async (kind) => {
legacy()
install()
const path = join(dir, '.zcode', 'v2', 'provider_config.json')
if (kind === 'unsupported') {
writeFileSync(
path,
JSON.stringify({
schemaVersion: 1,
config: {
defaultModelSelection: { providerId: 'account:zai-team-coding-plan', modelId: 'GLM' }
}
})
)
}
if (kind === 'missing-selection') {
writeFileSync(path, JSON.stringify({ schemaVersion: 1, config: {} }))
}
if (kind === 'future-schema') {
writeFileSync(path, JSON.stringify({ schemaVersion: 2, config: {} }))
}
if (kind === 'invalid-json') {
writeFileSync(path, 'not-json')
}
if (kind === 'bad-ciphertext') {
writeFileSync(
join(dir, '.zcode', 'v2', 'credentials.json'),
JSON.stringify({
'account-provider:account:zai-individual-coding-plan:identity': 'enc:v1:broken'
})
)
}
if (kind === 'missing-key') {
writeFileSync(join(dir, '.zcode', 'v2', 'credentials.json'), '{}')
}
const result = await fetchZcodeRateLimits({ credentialHost: host })
expect(['unavailable', 'error']).toContain(result.status)
expect(result.session).toBeNull()
expect(fetch).not.toHaveBeenCalled()
expect(JSON.stringify(result)).not.toContain('synthetic-legacy-key')
})
it('honors custom secret and does not decrypt another host secret', () => {
install()
expect(readZcodeV2PlanCredential(host).status).toBe('ok')
expect(
readZcodeV2PlanCredential({
...host,
env: { ...host.env, ZCODE_CREDENTIAL_SECRET: 'wrong-secret' }
})
).toEqual({ status: 'error' })
})
it('honors the upstream personal config override independently of the credential directory', () => {
install()
const selected = join(dir, 'selected.json')
cpSync(join(dir, '.zcode', 'v2', 'provider_config.json'), selected)
writeFileSync(join(dir, '.zcode', 'v2', 'provider_config.json'), 'invalid-default')
expect(
readZcodeV2PlanCredential({
...host,
env: { ...host.env, ZCODE_PERSONAL_PROVIDER_CONFIG_FILE: '~/selected.json' }
})
).toMatchObject({ status: 'ok', configPath: selected })
})
it('accepts upstream plaintext compatibility and rejects header injection without a request', async () => {
install()
const path = join(dir, '.zcode', 'v2', 'credentials.json')
const identityKey = 'account-provider:account:zai-individual-coding-plan:identity'
const apiKey =
'account-provider:coding-plan:account:zai-individual-coding-plan:account:synthetic:api-key'
writeFileSync(
path,
JSON.stringify({ [identityKey]: 'synthetic', [apiKey]: 'synthetic-plain-key' })
)
expect(readZcodeV2PlanCredential(host)).toMatchObject({
status: 'ok',
apiKey: 'synthetic-plain-key'
})
writeFileSync(
path,
JSON.stringify({ [identityKey]: 'synthetic', [apiKey]: 'key\r\nInjected: value' })
)
expect((await fetchZcodeRateLimits({ credentialHost: host })).status).toBe('error')
expect(fetch).not.toHaveBeenCalled()
})
it('uses execution-host identity, not data-directory identity, for the fallback cipher', () => {
install('fallback-host')
const fallback = { ...host, home: '/synthetic/home', env: { ZCODE_DATA_BASE_DIR: dir } }
expect(readZcodeV2PlanCredential(fallback)).toMatchObject({
status: 'ok',
apiKey: 'synthetic-fallback-host-key'
})
for (const changed of [
{ home: '/other/home' },
{ platform: 'win32' },
{ username: 'other-user' }
]) {
expect(readZcodeV2PlanCredential({ ...fallback, ...changed })).toEqual({ status: 'error' })
}
})
it('does not borrow credentials from another execution host', () => {
install()
const other = join(dir, 'other-host')
mkdirSync(other)
expect(
readZcodeV2PlanCredential({
...host,
home: other,
env: { ZCODE_DATA_BASE_DIR: other, ZCODE_CREDENTIAL_SECRET: 'synthetic-secret' }
})
).toEqual({ status: 'absent' })
})
it('preserves the protected Orca key priority even if v2 is corrupt', async () => {
install()
writeFileSync(join(dir, '.zcode', 'v2', 'credentials.json'), 'broken')
const result = await fetchZcodeRateLimits({
credentialHost: host,
planCredential: { apiKey: 'synthetic-orca-key', baseUrl: 'https://open.bigmodel.cn' }
})
expect(result.status).toBe('ok')
expect(result.usageMetadata?.credentialSource).toBe('orca-plan')
})
it.each(['site', 'account', 'corrupt', 'network-error'])(
'drops an in-flight quota when %s changes',
async (change) => {
install()
vi.mocked(fetch).mockImplementationOnce(async () => {
if (change === 'site') {
install('bigmodel')
} else if (change === 'account') {
install('zai-other')
} else {
writeFileSync(join(dir, '.zcode', 'v2', 'credentials.json'), 'broken')
}
if (change === 'network-error') {
throw new Error('Synthetic transport failure')
}
return quota()
})
const result = await fetchZcodeRateLimits({ credentialHost: host })
expect(result.status).toBe('unavailable')
expect(result.session).toBeNull()
}
)
})
describe('external v2 file boundaries', () => {
it.each(['provider_config.json', 'credentials.json'])(
'refuses oversized %s without legacy fallback',
(name) => {
install()
legacy()
const path = join(dir, '.zcode', 'v2', name)
const record = JSON.parse(readFileSync(path, 'utf8'))
record['unrelated'] = 'x'.repeat(4 * 1024 * 1024)
writeFileSync(path, JSON.stringify(record))
expect(readZcodeUsageCredentials({ credentialHost: host }).status).toBe('error')
}
)
it.each(['provider_config.json', 'credentials.json'])('refuses a directory at %s', (name) => {
install()
const path = join(dir, '.zcode', 'v2', name)
rmSync(path)
mkdirSync(path)
expect(readZcodeV2PlanCredential(host).status).toBe('error')
})
it.skipIf(process.platform === 'win32').each([
{ name: 'provider_config.json', replace: false },
{ name: 'credentials.json', replace: false },
{ name: 'provider_config.json', replace: true },
{ name: 'credentials.json', replace: true }
])(
'settles FIFO $name with replacement $replace in a timeout-fenced child',
async ({ name, replace }) => {
install()
const path = join(dir, '.zcode', 'v2', name)
const fifoPath = replace ? join(dir, 'replacement.fifo') : path
if (!replace) {
rmSync(path)
}
expect((await runProcess({ program: 'mkfifo', args: [fifoPath] })).code).toBe(0)
const bundle = buildSync({
entryPoints: [join(import.meta.dirname, 'zcode-v2-plan-credentials.ts')],
bundle: true,
platform: 'node',
format: 'cjs',
write: false
}).outputFiles[0]!.text
const setup = replace
? `const fs=require('node:fs'),originalStat=fs.statSync;fs.statSync=(path,...args)=>{const evidence=originalStat(path,...args);if(path===${JSON.stringify(path)})fs.renameSync(${JSON.stringify(fifoPath)},path);return evidence};`
: ''
const script = join(dir, 'read.cjs')
writeFileSync(
script,
`${bundle}\n${setup}\nconsole.log(module.exports.readZcodeV2PlanCredential(${JSON.stringify(host)}).status)`
)
const result = await runProcess({
program: process.execPath,
args: [script],
timeoutMs: 2000
})
expect(result.timedOut).toBe(false)
expect(result.code).toBe(0)
expect(result.stdout.trim()).toBe('error')
}
)
it.each([
{ options: { reasoningLevel: 42 } },
{ options: { reasoningLevel: '' } },
{ options: { reasoningLevel: ' ' } },
{ options: { extra: true } },
{ extra: true },
{ options: null }
])('rejects upstream-invalid selection %j without legacy fallback', (changed) => {
install()
legacy()
const path = join(dir, '.zcode', 'v2', 'provider_config.json')
const config = JSON.parse(readFileSync(path, 'utf8'))
Object.assign(config.config.defaultModelSelection, changed)
writeFileSync(path, JSON.stringify(config))
expect(readZcodeUsageCredentials({ credentialHost: host }).status).toBe('error')
})
it('accepts a valid optional reasoning level', () => {
install()
const path = join(dir, '.zcode', 'v2', 'provider_config.json')
const config = JSON.parse(readFileSync(path, 'utf8'))
config.config.defaultModelSelection.options = { reasoningLevel: ' high ' }
writeFileSync(path, JSON.stringify(config))
expect(readZcodeV2PlanCredential(host).status).toBe('ok')
})
it.each([
{ providerConfigRules: null },
{ providerConfigRules: { providerRules: 42 } },
{ providerConfigRules: { providerRules: [], unknown: true } },
{ modelConfigRules: null },
{ modelConfigRules: { providerModelRules: 42, manualProviderModelRules: [] } },
{ modelConfigRules: { providerModelRules: [], manualProviderModelRules: [null] } },
{ modelConfigRules: { providerModelRules: [{}], manualProviderModelRules: [] } }
])('rejects upstream-invalid rule containers %j without legacy fallback', (changed) => {
install()
legacy()
const path = join(dir, '.zcode', 'v2', 'provider_config.json')
const config = JSON.parse(readFileSync(path, 'utf8'))
Object.assign(config.config, changed)
writeFileSync(path, JSON.stringify(config))
expect(readZcodeUsageCredentials({ credentialHost: host }).status).toBe('error')
})
it('accepts a valid selected-provider empty personal rule and smart model rule', () => {
install('bigmodel')
const path = join(dir, '.zcode', 'v2', 'provider_config.json')
const config = JSON.parse(readFileSync(path, 'utf8'))
const selection = config.config.defaultModelSelection
config.config.providerConfigRules.providerRules = [
{ providerId: selection.providerId, config: {} }
]
config.config.modelConfigRules.providerModelRules = [{ ...selection, config: {} }]
writeFileSync(path, JSON.stringify(config))
expect(readZcodeV2PlanCredential(host)).toMatchObject({
status: 'ok',
baseUrl: 'https://open.bigmodel.cn'
})
})
it.each([
{ access: { type: 'api-key', apiKey: 'synthetic-personal-key' } },
{ access: null },
{ api: { type: 'bad-api-type', baseUrl: 'invalid-url' } },
{ api: { baseUrl: 'https://valid.example' } },
{ visibility: 'hidden' }
])(
'refuses unvalidated provider config %j without reading the vault or querying quota',
async (personal) => {
install('bigmodel')
legacy()
const path = join(dir, '.zcode', 'v2', 'provider_config.json')
const config = JSON.parse(readFileSync(path, 'utf8'))
config.config.providerConfigRules.providerRules = [
{ providerId: config.config.defaultModelSelection.providerId, config: personal }
]
writeFileSync(path, JSON.stringify(config))
rmSync(join(dir, '.zcode', 'v2', 'credentials.json'))
expect(readZcodeUsageCredentials({ credentialHost: host }).status).toBe('unavailable')
expect((await fetchZcodeRateLimits({ credentialHost: host })).status).toBe('unavailable')
expect(fetch).not.toHaveBeenCalled()
expect(readFileSync(path, 'utf8')).toBe(JSON.stringify(config))
expect(
(
await fetchZcodeRateLimits({
credentialHost: host,
planCredential: {
apiKey: 'synthetic-orca-key',
baseUrl: 'https://api.z.ai'
}
})
).status
).toBe('ok')
}
)
it.each(['duplicate-provider', 'malformed-model', 'valid-unsupported-model', 'manual-model'])(
'cannot establish selected credentials from %s rules',
(kind) => {
install('bigmodel')
legacy()
const path = join(dir, '.zcode', 'v2', 'provider_config.json')
const config = JSON.parse(readFileSync(path, 'utf8'))
const selection = config.config.defaultModelSelection
if (kind === 'duplicate-provider') {
config.config.providerConfigRules.providerRules = Array.from({ length: 2 }, () => ({
providerId: selection.providerId,
config: {}
}))
} else {
const group = kind === 'manual-model' ? 'manualProviderModelRules' : 'providerModelRules'
config.config.modelConfigRules[group] = [
{
...selection,
config:
kind === 'manual-model'
? {}
: {
optionSpecs: {
reasoningLevel: { values: kind === 'malformed-model' ? 42 : ['high'] }
}
}
}
]
}
writeFileSync(path, JSON.stringify(config))
expect(readZcodeUsageCredentials({ credentialHost: host }).status).toBe(
kind === 'duplicate-provider' ? 'error' : 'unavailable'
)
}
)
})
it('leaves encrypted CLI files unchanged across repeated account reads', () => {
install()
const root = join(dir, '.zcode', 'v2')
const before = new Map(readdirSync(root).map((name) => [name, readFileSync(join(root, name))]))
expect(before.get('credentials.json')?.toString()).toContain('enc:v1:')
for (let index = 0; index < 25; index++) {
expect(readZcodeV2PlanCredential(host).status).toBe('ok')
}
expect(readdirSync(root)).toEqual([...before.keys()])
for (const [name, bytes] of before) {
expect(readFileSync(join(root, name))).toEqual(bytes)
}
})
+135
View File
@@ -0,0 +1,135 @@
import { createDecipheriv, createHash } from 'node:crypto'
import { homedir, platform, userInfo } from 'node:os'
import { join, resolve } from 'node:path'
import { z } from 'zod'
import { readNodeFileSyncWithinLimit } from '../../shared/node-bounded-file-reader'
import { ZCODE_PLAN_SITE_BASE_URLS } from '../../shared/zcode-plan-sites'
import { readSupportedZcodeV2Selection } from './zcode-v2-personal-overlay'
const MAX_V2_FILE_BYTES = 4 * 1024 * 1024
function readV2File(path: string): string {
return readNodeFileSyncWithinLimit(path, MAX_V2_FILE_BYTES, {
regularFileOnly: true
}).buffer.toString('utf8')
}
const credentialSchema = z.record(z.string(), z.string())
const sites = new Map([
['account:zai-individual-coding-plan', 'zai'],
['account:bigmodel-individual-coding-plan', 'bigmodel']
] as const)
export type ZcodeCredentialHost = {
home: string
platform: string
username: string
env: NodeJS.ProcessEnv
}
export type ZcodeV2PlanCredential =
| { status: 'absent' | 'unavailable' | 'error' }
| { status: 'ok'; apiKey: string; baseUrl: string; identity: string; configPath: string }
export function localZcodeCredentialHost(): ZcodeCredentialHost {
let username = 'unknown'
try {
username = userInfo().username
} catch {
// Upstream uses the same fallback in sandboxed hosts.
}
return { home: homedir(), platform: platform(), username, env: process.env }
}
function userPath(value: string, home: string): string {
if (value === '~') {
return home
}
return value.startsWith('~/') ? join(home, value.slice(2)) : resolve(value)
}
// Read-only enc:v1 compatibility with ZCode 29628c9 credential-cipher.ts.
function decrypt(value: string, host: ZcodeCredentialHost): string {
if (!value.startsWith('enc:v1:')) {
return value
}
const parts = value.slice(7).split('.')
const [nonce, tag, ciphertext] = parts
if (parts.length !== 3 || !nonce || !tag || !ciphertext) {
throw new Error('Invalid credential')
}
const iv = Buffer.from(nonce, 'base64url')
const authTag = Buffer.from(tag, 'base64url')
if (iv.length !== 12 || authTag.length !== 16) {
throw new Error('Invalid credential')
}
const secret =
host.env.ZCODE_CREDENTIAL_SECRET?.trim() ||
`zcode-credential-fallback:${host.platform}:${host.home}:${host.username}`
const key = createHash('sha256').update(secret).digest()
const cipher = createDecipheriv('aes-256-gcm', key, iv)
cipher.setAuthTag(authTag)
return Buffer.concat([
cipher.update(Buffer.from(ciphertext, 'base64url')),
cipher.final()
]).toString('utf8')
}
export function readZcodeV2PlanCredential(
host = localZcodeCredentialHost()
): ZcodeV2PlanCredential {
const base = userPath(host.env.ZCODE_DATA_BASE_DIR ?? host.home, host.home)
const configPath =
(host.env.ZCODE_PERSONAL_PROVIDER_CONFIG_FILE?.trim()
? userPath(host.env.ZCODE_PERSONAL_PROVIDER_CONFIG_FILE.trim(), host.home)
: '') || join(base, '.zcode', 'v2', 'provider_config.json')
let rawConfig: string
try {
rawConfig = readV2File(configPath)
} catch (error) {
return {
status:
error instanceof Error && 'code' in error && error.code === 'ENOENT' ? 'absent' : 'error'
}
}
try {
const selection = readSupportedZcodeV2Selection(JSON.parse(rawConfig))
if (!selection) {
return { status: 'unavailable' }
}
const site = [...sites].find(([provider]) => provider === selection.providerId)?.[1]
if (!site) {
return { status: 'unavailable' }
}
const store = credentialSchema.parse(
JSON.parse(readV2File(join(base, '.zcode', 'v2', 'credentials.json')))
)
const rawIdentity = store[`account-provider:${selection.providerId}:identity`]
if (!rawIdentity) {
return { status: 'unavailable' }
}
const identity = decrypt(rawIdentity, host).trim()
if (!identity) {
return { status: 'unavailable' }
}
const rawKey =
store[
`account-provider:coding-plan:${selection.providerId}:account:${encodeURIComponent(identity)}:api-key`
]
if (!rawKey) {
return { status: 'unavailable' }
}
const apiKey = decrypt(rawKey, host).trim()
if (!apiKey || /[\r\n]/.test(apiKey)) {
return { status: 'error' }
}
return {
status: 'ok',
apiKey,
baseUrl: ZCODE_PLAN_SITE_BASE_URLS[site],
identity: JSON.stringify([selection.providerId, identity]),
configPath
}
} catch {
return { status: 'error' }
}
}
@@ -160,7 +160,7 @@ export function ZcodePlanAccountsSection(): React.JSX.Element {
<p className="text-xs text-muted-foreground">
{translate(
'auto.components.settings.ZcodePlanAccountsSection.usingCliHelp',
'Orca reads the Coding Plan key from ~/.zcode/cli/config.json. Save an API key below to link the plan here instead.'
'Orca reads the selected Coding Plan from the ZCode CLI credential store on this computer. Save an API key below to link the plan here instead.'
)}
</p>
</>
+1 -1
View File
@@ -12654,7 +12654,7 @@
"keyStored": "API key saved · {{value0}}",
"keyStoredHelp": "Stored locally and sent only to the selected site for usage refreshes. It takes priority over the ZCode CLI sign-in.",
"usingCli": "Using the ZCode CLI sign-in",
"usingCliHelp": "Orca reads the Coding Plan key from ~/.zcode/cli/config.json. Save an API key below to link the plan here instead.",
"usingCliHelp": "Orca reads the selected Coding Plan from the ZCode CLI credential store on this computer. Save an API key below to link the plan here instead.",
"notConfigured": "No GLM Coding Plan linked",
"notConfiguredHelp": "Save the plan API key below, or sign in with the ZCode CLI on this computer.",
"refreshUsage": "Refresh usage",
+1 -1
View File
@@ -12268,7 +12268,7 @@
"keyStored": "API key 已保存 · {{value0}}",
"keyStoredHelp": "仅保存在本地,并只发送到所选站点用于用量刷新。优先级高于 ZCode CLI 登录。",
"usingCli": "正在使用 ZCode CLI 登录",
"usingCliHelp": "Orca 从 ~/.zcode/cli/config.json 读取 Coding Plan key。在下方保存 API key 即可改为在此关联套餐。",
"usingCliHelp": "Orca 会从此电脑上的 ZCode CLI 凭据存储中读取所选编程套餐。您也可以在下方保存 API 密钥以直接关联套餐。",
"notConfigured": "尚未关联 GLM Coding Plan",
"notConfiguredHelp": "在下方保存套餐 API key,或在这台电脑上通过 ZCode CLI 登录。",
"refreshUsage": "刷新用量",
@@ -1,9 +1,14 @@
import { closeSync, ftruncateSync, mkdtempSync, openSync, rmSync, writeFileSync } from 'node:fs'
import * as fs from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { NodeFileReadTooLargeError, readNodeFileSyncWithinLimit } from './node-bounded-file-reader'
vi.mock('node:fs', async (importOriginal) => ({
...(await importOriginal<typeof fs>())
}))
const tempDirectories: string[] = []
function createTempFile(content: string): string {
@@ -15,6 +20,7 @@ function createTempFile(content: string): string {
}
afterEach(() => {
vi.restoreAllMocks()
for (const directory of tempDirectories.splice(0)) {
rmSync(directory, { recursive: true })
}
@@ -36,3 +42,34 @@ describe('readNodeFileSyncWithinLimit', () => {
expect(() => readNodeFileSyncWithinLimit(path, 1024)).toThrow(NodeFileReadTooLargeError)
})
})
describe('regular file growth evidence', () => {
it('refuses growth beyond the byte limit during a read', () => {
const path = createTempFile('abcd')
const original = fs.readSync
const spy = vi.spyOn(fs, 'readSync').mockImplementation((...args) => {
const bytes = original(...args)
if (spy.mock.calls.length === 1) {
fs.appendFileSync(path, 'x'.repeat(1024))
}
return bytes
})
expect(() => readNodeFileSyncWithinLimit(path, 1024, { regularFileOnly: true })).toThrow(
NodeFileReadTooLargeError
)
})
it('validates the opened descriptor when the path evidence changes', () => {
const path = createTempFile('abcd')
const original = fs.statSync
vi.spyOn(fs, 'statSync').mockImplementation((...args) => {
const evidence = original(...args)
fs.unlinkSync(path)
fs.mkdirSync(path)
return evidence
})
expect(() => readNodeFileSyncWithinLimit(path, 1024, { regularFileOnly: true })).toThrow(
'Expected a regular file'
)
})
})
+14 -3
View File
@@ -1,4 +1,4 @@
import { closeSync, constants, fstatSync, openSync, readSync, type Stats } from 'node:fs'
import { closeSync, constants, fstatSync, openSync, readSync, statSync, type Stats } from 'node:fs'
import { open, stat, type FileHandle } from 'node:fs/promises'
const MIN_GROWTH_BYTES = 64 * 1024
@@ -108,15 +108,26 @@ export async function readNodeFileHandleWithinLimit(
export function readNodeFileSyncWithinLimit(
filePath: string,
maxBytes: number
maxBytes: number,
options: { regularFileOnly?: boolean } = {}
): BoundedNodeFileRead {
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
throw new RangeError('File read limit must be a non-negative safe integer')
}
const descriptor = openSync(filePath, 'r')
if (options.regularFileOnly && !statSync(filePath).isFile()) {
throw new Error('Expected a regular file')
}
// Nonblocking open also fences replacement with a FIFO after the path check.
const flags = options.regularFileOnly
? constants.O_RDONLY | (process.platform === 'win32' ? 0 : constants.O_NONBLOCK)
: 'r'
const descriptor = openSync(filePath, flags)
try {
const stats = fstatSync(descriptor)
if (options.regularFileOnly && !stats.isFile()) {
throw new Error('Expected a regular file')
}
validateSize(stats.size, maxBytes)
let buffer = Buffer.allocUnsafe(stats.size)
+6 -1
View File
@@ -9,10 +9,13 @@ const RESTRICTED_ENV_KEYS = new Set([
'HOMEPATH',
'CODEX_HOME',
'ORCA_CODEX_HOME',
'ZCODE_DATA_BASE_DIR',
'ZCODE_PERSONAL_PROVIDER_CONFIG_FILE',
// Why: Orca's spawn hook writes Claude folder trust into the config this names.
'CLAUDE_CONFIG_DIR',
'ORCA_E2E_USER_DATA_DIR',
'ORCA_E2E_HOME_DIR',
'ORCA_DISABLE_MACOS_LOGIN_SHELL',
'ZDOTDIR',
'ORCA_ORIG_ZDOTDIR',
'BASH_ENV',
@@ -98,7 +101,9 @@ export function createElectronHomeIsolation({
HOME: isolatedHome,
USERPROFILE: isolatedHome,
ORCA_E2E_USER_DATA_DIR: userDataDir,
ORCA_E2E_HOME_DIR: isolatedHome
ORCA_E2E_HOME_DIR: isolatedHome,
// macOS login resets HOME to the native account before the shell starts.
...(process.platform === 'darwin' ? { ORCA_DISABLE_MACOS_LOGIN_SHELL: '1' } : {})
}
}
}
@@ -1,7 +1,8 @@
import { mkdtempSync, realpathSync, rmSync } from 'node:fs'
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { readZcodeV2PlanCredential } from '../../../src/main/zcode/zcode-v2-plan-credentials'
import {
areSameHomePath,
assertElectronResolvedIsolatedHome,
@@ -11,6 +12,7 @@ import {
const tempDirs: string[] = []
afterEach(() => {
vi.unstubAllGlobals()
for (const tempDir of tempDirs.splice(0)) {
rmSync(tempDir, { recursive: true, force: true })
}
@@ -33,6 +35,7 @@ describe('createElectronHomeIsolation', () => {
ORCA_CODEX_HOME: '/real/orca-codex',
CLAUDE_CONFIG_DIR: '/real/claude',
ZDOTDIR: '/real/zdotdir',
ORCA_DISABLE_MACOS_LOGIN_SHELL: 'false',
PATH: '/bin'
},
launchEnv: { TEST_TOKEN: 'safe' },
@@ -57,6 +60,9 @@ describe('createElectronHomeIsolation', () => {
expect(isolation.env.ORCA_CODEX_HOME).toBeUndefined()
expect(isolation.env.CLAUDE_CONFIG_DIR).toBeUndefined()
expect(isolation.env.ZDOTDIR).toBeUndefined()
expect(isolation.env.ORCA_DISABLE_MACOS_LOGIN_SHELL).toBe(
process.platform === 'darwin' ? '1' : undefined
)
// Codex always routes to the resolved home, so the post-launch guard must
// accept the boundary this env produces.
expect(() =>
@@ -89,4 +95,133 @@ describe('createElectronHomeIsolation', () => {
it('compares Windows home paths case-insensitively', () => {
expect(areSameHomePath('C:\\Users\\Alice', 'c:\\users\\alice', 'win32')).toBe(true)
})
it.each(['launchEnv', 'extraEnv'] as const)(
'rejects %s attempts to restore the macOS login wrapper',
(overlay) => {
expect(() =>
createElectronHomeIsolation({
inheritedEnv: {},
launchEnv: overlay === 'launchEnv' ? { ORCA_DISABLE_MACOS_LOGIN_SHELL: '0' } : {},
extraEnv: overlay === 'extraEnv' ? { ORCA_DISABLE_MACOS_LOGIN_SHELL: '0' } : {},
userDataDir: createUserDataDir(),
realHome: '/real/home'
})
).toThrow(/ORCA_DISABLE_MACOS_LOGIN_SHELL/)
}
)
})
function installSyntheticPlan(home: string, apiKey: string): string {
const dir = path.join(home, '.zcode', 'v2')
mkdirSync(dir, { recursive: true })
const providerId = 'account:bigmodel-individual-coding-plan'
const configPath = path.join(dir, 'provider_config.json')
writeFileSync(
configPath,
JSON.stringify({
schemaVersion: 1,
config: {
providerConfigRules: { providerRules: [] },
modelConfigRules: { providerModelRules: [], manualProviderModelRules: [] },
defaultModelSelection: { providerId, modelId: 'GLM' }
}
})
)
writeFileSync(
path.join(dir, 'credentials.json'),
JSON.stringify({
[`account-provider:${providerId}:identity`]: 'synthetic',
[`account-provider:coding-plan:${providerId}:account:synthetic:api-key`]: apiKey
})
)
return configPath
}
describe('ZCode credentials in the disposable home', () => {
const credentialRoots = ['ZCODE_DATA_BASE_DIR', 'ZCODE_PERSONAL_PROVIDER_CONFIG_FILE'] as const
it.each(credentialRoots)('strips inherited %s before reading an isolated plan', (key) => {
const externalHome = createUserDataDir()
const externalConfig = installSyntheticPlan(externalHome, 'synthetic-external-key')
const isolation = createElectronHomeIsolation({
inheritedEnv: {
[key]: key === 'ZCODE_DATA_BASE_DIR' ? externalHome : externalConfig
},
launchEnv: {},
extraEnv: {},
userDataDir: createUserDataDir(),
realHome: externalHome
})
const configPath = installSyntheticPlan(isolation.isolatedHome, 'synthetic-isolated-key')
const fetch = vi.fn(() => {
throw new Error('Credential isolation must not use the network')
})
vi.stubGlobal('fetch', fetch)
expect(
readZcodeV2PlanCredential({
home: isolation.isolatedHome,
platform: process.platform,
username: 'synthetic-user',
env: isolation.env
})
).toMatchObject({ status: 'ok', apiKey: 'synthetic-isolated-key', configPath })
expect(isolation.env[key]).toBeUndefined()
expect(fetch).not.toHaveBeenCalled()
})
it.each(['launchEnv', 'extraEnv'] as const)('rejects %s credential-root overlays', (overlay) => {
const externalHome = createUserDataDir()
const externalConfig = installSyntheticPlan(externalHome, 'synthetic-external-key')
for (const key of credentialRoots) {
for (const spelling of [key, key.toLowerCase()]) {
const override = {
[spelling]: key === 'ZCODE_DATA_BASE_DIR' ? externalHome : externalConfig
}
expect(() =>
createElectronHomeIsolation({
inheritedEnv: {},
launchEnv: overlay === 'launchEnv' ? override : {},
extraEnv: overlay === 'extraEnv' ? override : {},
userDataDir: createUserDataDir(),
realHome: externalHome
})
).toThrow(
`${overlay === 'launchEnv' ? 'launchEnv' : 'orcaAppExtraEnv'}.${spelling} cannot override the E2E home boundary`
)
}
}
})
it('reads ordinary home fixtures while retaining legitimate launch overrides', () => {
const isolation = createElectronHomeIsolation({
inheritedEnv: { PATH: '/synthetic/bin' },
launchEnv: { ORCA_DISABLE_AGENT_HOOKS: '1', TEST_TOKEN: 'launch' },
extraEnv: { TEST_TOKEN: 'extra', ZCODE_CREDENTIAL_SECRET: 'synthetic-secret' },
userDataDir: createUserDataDir(),
realHome: createUserDataDir()
})
const configPath = installSyntheticPlan(isolation.isolatedHome, 'synthetic-ordinary-key')
vi.stubGlobal(
'fetch',
vi.fn(() => {
throw new Error('Network forbidden')
})
)
expect(isolation.env).toMatchObject({
PATH: '/synthetic/bin',
ORCA_DISABLE_AGENT_HOOKS: '1',
TEST_TOKEN: 'extra',
ZCODE_CREDENTIAL_SECRET: 'synthetic-secret'
})
expect(
readZcodeV2PlanCredential({
home: isolation.isolatedHome,
platform: process.platform,
username: 'synthetic-user',
env: isolation.env
})
).toMatchObject({ status: 'ok', apiKey: 'synthetic-ordinary-key', configPath })
expect(fetch).not.toHaveBeenCalled()
})
})
+1 -1
View File
@@ -340,7 +340,7 @@ export const test = base.extend<OrcaTestFixtures, OrcaWorkerFixtures>({
if (!store) {
return false
}
await store.getState().fetchRepos()
await store.getState().fetchRepos({ runtimeEnvironmentId: null })
const repo = store.getState().repos.find((candidate) => candidate.id === repoId)
if (!repo) {
return false
+93
View File
@@ -0,0 +1,93 @@
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import { test, expect } from './helpers/orca-app'
import { waitForSessionReady } from './helpers/store'
function selectPlan(home: string, site: 'zai' | 'bigmodel'): void {
const root = path.join(home, '.zcode', 'v2')
mkdirSync(root, { recursive: true })
const providerId = `account:${site}-individual-coding-plan`
writeFileSync(
path.join(root, 'provider_config.json'),
JSON.stringify({
schemaVersion: 1,
config: {
providerOrder: [],
providerConfigRules: { providerRules: [] },
modelConfigRules: { providerModelRules: [], manualProviderModelRules: [] },
defaultModelSelection: { providerId, modelId: 'GLM-test' }
}
})
)
writeFileSync(
path.join(root, 'credentials.json'),
JSON.stringify({
[`account-provider:${providerId}:identity`]: 'synthetic-account',
[`account-provider:coding-plan:${providerId}:account:synthetic-account:api-key`]: `synthetic-${site}-key`
})
)
}
test('Accounts recognizes the selected CLI plan and preserves a separately saved quota key', async ({
orcaPage,
electronApp
}, testInfo) => {
await waitForSessionReady(orcaPage)
const home = await electronApp.evaluate(({ app }) => app.getPath('home'))
await electronApp.evaluate(() => {
const realFetch = globalThis.fetch
globalThis.fetch = async (input, init) => {
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
if (url.endsWith('/api/monitor/usage/quota/limit')) {
const key = new Headers(init?.headers).get('Authorization')
const percentage =
key === 'synthetic-zai-key' ? 12 : key === 'synthetic-bigmodel-key' ? 67 : 33
return new Response(
JSON.stringify({
success: true,
data: { limits: [{ type: 'TOKENS_LIMIT', unit: 3, number: 5, percentage }] }
}),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)
}
return realFetch(input, init)
}
})
await orcaPage.evaluate(() => {
const state = window.__store?.getState()
state?.openSettingsPage()
state?.openSettingsTarget({ pane: 'accounts', repoId: null, sectionId: 'accounts-zcode' })
})
const section = orcaPage.locator('#accounts-zcode')
await expect(section).toBeVisible({ timeout: 30_000 })
await expect(section.getByText('No GLM Coding Plan linked', { exact: true })).toBeVisible()
await section.screenshot({ path: testInfo.outputPath('01-no-cli-plan.png') })
selectPlan(home, 'zai')
await orcaPage.evaluate(() => window.__store?.getState().refreshRateLimits())
await expect(section.getByText('Using the ZCode CLI sign-in', { exact: true })).toBeVisible({
timeout: 30_000
})
await expect(section.getByText('12%', { exact: true })).toBeVisible()
await section.screenshot({ path: testInfo.outputPath('02-cli-plan.png') })
selectPlan(home, 'bigmodel')
await orcaPage.evaluate(() => window.__store?.getState().refreshRateLimits())
await expect(section.getByText('67%', { exact: true })).toBeVisible({ timeout: 30_000 })
await expect(section.getByText('12%', { exact: true })).toHaveCount(0)
await section.screenshot({ path: testInfo.outputPath('03-selected-account.png') })
await section.getByPlaceholder('Paste your GLM Coding Plan API key').fill('synthetic-saved-key')
await section.getByRole('button', { name: 'Save', exact: true }).click()
await expect(section.getByText(/API key saved ·/)).toBeVisible({ timeout: 15_000 })
await expect(section.getByText('33%', { exact: true })).toBeVisible({ timeout: 30_000 })
const savedPath = path.join(home, '.orca', 'zcode-plan-api-key.enc')
expect(existsSync(savedPath)).toBe(true)
await section.screenshot({ path: testInfo.outputPath('04-saved-key-priority.png') })
await section.getByRole('button', { name: 'Forget key', exact: true }).click()
await expect(section.getByText('Using the ZCode CLI sign-in', { exact: true })).toBeVisible({
timeout: 15_000
})
await expect(section.getByText('67%', { exact: true })).toBeVisible({ timeout: 30_000 })
expect(existsSync(savedPath)).toBe(false)
})