Skip to content

Distinct account config files can resolve to the same state file #89

Description

@iceteaSA

Found during a source audit of 9bf8f4c. Verified by reading the code. A PR is incoming.

Two separate path-resolution problems in packages/opencode/src/core/account-paths.ts, both ending with two documents fighting over one file.

1. The state path is derived with a suffix test

export function deriveStatePath(configPath: string): string {
  return configPath.endsWith(ACCOUNT_FILE_NAME)
    ? join(dirname(configPath), ACCOUNT_STATE_FILE_NAME)
    : `${configPath}.state.json`
}

ACCOUNT_FILE_NAME is openai-auth.json, and endsWith matches any filename ending in it:

OPENCODE_OPENAI_AUTH_FILE derived state path
/tmp/openai-auth.json /tmp/openai-auth-state.json
/tmp/team-openai-auth.json /tmp/openai-auth-state.json

Two different rosters in one directory silently share one state document. The second writer overwrites the first's stored per-account fields, which invalidates those accounts.

The module's own header comment describes this hazard class:

if the copies ever drifted, a writer would take a lock on one path while writing another — mutual exclusion silently lost on a file holding credentials

A basename comparison instead of endsWith keeps distinct config paths mapping to distinct state paths.

2. An explicit state path is accepted even if it equals the config path

export function getAccountStatePath(configPath = getAccountStoragePath()) {
  const explicit = process.env.OPENCODE_OPENAI_AUTH_STATE_FILE?.trim()
  if (explicit) return explicit
  return deriveStatePath(configPath)
}

Nothing checks the override against the config path. Point OPENCODE_OPENAI_AUTH_FILE and OPENCODE_OPENAI_AUTH_STATE_FILE at the same file and a mutation writes the config document and then the state document to it — the second write replaces the roster with a state-only document.

Affected

packages/opencode/src/core/account-paths.ts at 9bf8f4c, lines 38-48.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions