mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 08:03:13 +00:00
fix(codex): validate linked worktree trust roots
This commit is contained in:
@@ -137,7 +137,7 @@ describe('markCopilotFolderTrusted', () => {
|
||||
})
|
||||
|
||||
describe('markCodexProjectTrusted', () => {
|
||||
it('trusts the main repository root for a linked worktree', () => {
|
||||
it('trusts the main repository root for a linked worktree without reading commondir', () => {
|
||||
const fixtureRoot = mkdtempSync(join(tmpdir(), 'orca-codex-linked-ws-'))
|
||||
const repository = join(fixtureRoot, 'repo')
|
||||
const workspace = join(fixtureRoot, 'worktrees', 'feature')
|
||||
@@ -146,7 +146,7 @@ describe('markCodexProjectTrusted', () => {
|
||||
mkdirSync(worktreeGitDir, { recursive: true })
|
||||
mkdirSync(workspace, { recursive: true })
|
||||
writeFileSync(join(workspace, '.git'), `gitdir: ${worktreeGitDir}\n`, 'utf-8')
|
||||
writeFileSync(join(worktreeGitDir, 'commondir'), '../..\n', 'utf-8')
|
||||
writeFileSync(join(worktreeGitDir, 'gitdir'), join(workspace, '.git'), 'utf-8')
|
||||
|
||||
markCodexProjectTrusted(workspace)
|
||||
|
||||
@@ -171,6 +171,37 @@ describe('markCodexProjectTrusted', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('does not broaden trust through arbitrary or adversarial Git metadata', () => {
|
||||
const fixtureRoot = mkdtempSync(join(tmpdir(), 'orca-codex-untrusted-gitdir-'))
|
||||
const workspace = join(fixtureRoot, 'workspace')
|
||||
const arbitraryGitDir = join(fixtureRoot, 'metadata', 'feature')
|
||||
const unrelatedRoot = join(fixtureRoot, 'unrelated')
|
||||
try {
|
||||
mkdirSync(arbitraryGitDir, { recursive: true })
|
||||
mkdirSync(workspace, { recursive: true })
|
||||
mkdirSync(unrelatedRoot, { recursive: true })
|
||||
writeFileSync(join(workspace, '.git'), `gitdir: ${arbitraryGitDir}\n`, 'utf-8')
|
||||
writeFileSync(join(arbitraryGitDir, 'commondir'), join(unrelatedRoot, '.git'), 'utf-8')
|
||||
|
||||
markCodexProjectTrusted(workspace)
|
||||
const structuredGitDir = join(unrelatedRoot, '.git', 'worktrees', 'feature')
|
||||
mkdirSync(structuredGitDir, { recursive: true })
|
||||
writeFileSync(join(workspace, '.git'), `gitdir: ${structuredGitDir}\n`, 'utf-8')
|
||||
writeFileSync(join(structuredGitDir, 'gitdir'), join(unrelatedRoot, '.git'), 'utf-8')
|
||||
markCodexProjectTrusted(workspace)
|
||||
|
||||
const written = readFileSync(join(testState.fakeHomeDir, '.codex', 'config.toml'), 'utf-8')
|
||||
expect(written).toContain(
|
||||
`[projects."${escapeTomlBasicString(realpathSync.native(workspace))}"]`
|
||||
)
|
||||
expect(written).not.toContain(
|
||||
`[projects."${escapeTomlBasicString(realpathSync.native(unrelatedRoot))}"]`
|
||||
)
|
||||
} finally {
|
||||
rmSync(fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('writes ~/.codex/config.toml with the project marked trusted', () => {
|
||||
const workspace = mkdtempSync(join(tmpdir(), 'orca-codex-ws-'))
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { existsSync, mkdirSync, readFileSync, realpathSync } from 'node:fs'
|
||||
import { homedir } from 'node:os'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { basename, dirname, join, resolve } from 'node:path'
|
||||
import { writeFileAtomically } from './codex-accounts/fs-utils'
|
||||
import { getOrcaManagedCodexHomePath } from './codex/codex-home-paths'
|
||||
import { upsertProjectTrustLevel } from './codex/config-toml-trust'
|
||||
@@ -120,18 +120,34 @@ export function markCodexProjectTrusted(workspacePath: string): void {
|
||||
function resolveCodexProjectTrustRoot(workspacePath: string): string {
|
||||
const absPath = canonicalize(workspacePath)
|
||||
try {
|
||||
const gitDirReference = readFileSync(join(absPath, '.git'), 'utf-8')
|
||||
const gitDirMatch = /^gitdir:\s*(.+?)\s*$/im.exec(gitDirReference)
|
||||
if (!gitDirMatch?.[1]) {
|
||||
const gitDirReference = readFileSync(join(absPath, '.git'), 'utf-8').trim()
|
||||
if (!gitDirReference.startsWith('gitdir:')) {
|
||||
return absPath
|
||||
}
|
||||
const gitDir = resolve(absPath, gitDirMatch[1])
|
||||
const commonDirReference = readFileSync(join(gitDir, 'commondir'), 'utf-8').trim()
|
||||
if (!commonDirReference) {
|
||||
const gitDirPath = gitDirReference.slice('gitdir:'.length).trim()
|
||||
if (!gitDirPath) {
|
||||
return absPath
|
||||
}
|
||||
// Why: Codex resolves linked-worktree trust through commondir to the main repo root.
|
||||
return canonicalize(dirname(resolve(gitDir, commonDirReference)))
|
||||
const gitDir = resolve(absPath, gitDirPath)
|
||||
const worktreesDir = dirname(gitDir)
|
||||
if (basename(worktreesDir) !== 'worktrees') {
|
||||
return absPath
|
||||
}
|
||||
// Why: workspace-controlled .git metadata must not broaden trust without Git's reciprocal link.
|
||||
const gitDirBacklink = readFileSync(join(gitDir, 'gitdir'), 'utf-8').trim()
|
||||
if (!gitDirBacklink) {
|
||||
return absPath
|
||||
}
|
||||
const resolvedBacklink = resolve(gitDir, gitDirBacklink)
|
||||
const workspaceGitFile = join(absPath, '.git')
|
||||
if (
|
||||
resolvedBacklink !== workspaceGitFile &&
|
||||
canonicalize(resolvedBacklink) !== canonicalize(workspaceGitFile)
|
||||
) {
|
||||
return absPath
|
||||
}
|
||||
// Why: mirror Codex's validated .git/worktrees/<name> traversal instead of trusting arbitrary commondir contents.
|
||||
return canonicalize(dirname(dirname(worktreesDir)))
|
||||
} catch {
|
||||
return absPath
|
||||
}
|
||||
|
||||
+1
-1
@@ -619,7 +619,7 @@ function getLocalOrcaCodexHomeEnvKeysToDelete(env: Record<string, string>): stri
|
||||
return keysToDelete
|
||||
}
|
||||
|
||||
type GetSelectedCodexHomePath = (
|
||||
export type GetSelectedCodexHomePath = (
|
||||
target?: CodexAccountSelectionTarget,
|
||||
launchEnv?: NodeJS.ProcessEnv,
|
||||
launchContext?: { workspacePath?: string; launchAgent?: TuiAgent }
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
import { registerRepoHandlers } from '../ipc/repos'
|
||||
import { registerWorktreeHandlers } from '../ipc/worktrees'
|
||||
import { registerWorkspaceCleanupHandlers } from '../ipc/workspace-cleanup'
|
||||
import { getLocalPtyProvider, registerPtyHandlers } from '../ipc/pty'
|
||||
import { getLocalPtyProvider, registerPtyHandlers, type GetSelectedCodexHomePath } from '../ipc/pty'
|
||||
import { registerDaemonManagementHandlers } from '../ipc/pty-management'
|
||||
import { registerSshHandlers } from '../ipc/ssh'
|
||||
import { registerRemoteWorkspaceHandlers } from '../ipc/remote-workspace'
|
||||
@@ -39,7 +39,6 @@ import type { RuntimeMobileSessionTabMove } from '../../shared/runtime-types'
|
||||
import { isNativeFileDropPayload, type NativeFileDropPayload } from '../../shared/native-file-drop'
|
||||
import { requestMobileMarkdownFromRenderer } from './mobile-markdown-request-relay'
|
||||
import { requestTerminalTabCloseFromRenderer } from './terminal-tab-close-request-relay'
|
||||
import type { CodexAccountSelectionTarget } from '../codex-accounts/runtime-selection'
|
||||
import type { ClaudeAccountSelectionTarget } from '../claude-accounts/runtime-selection'
|
||||
import { runWorktreeChangeInvalidators } from '../ipc/worktree-change-invalidators'
|
||||
import {
|
||||
@@ -66,10 +65,7 @@ export function attachMainWindowServices(
|
||||
mainWindow: BrowserWindow,
|
||||
store: Store,
|
||||
runtime: OrcaRuntimeService,
|
||||
getSelectedCodexHomePath?: (
|
||||
target?: CodexAccountSelectionTarget,
|
||||
launchEnv?: NodeJS.ProcessEnv
|
||||
) => string | null,
|
||||
getSelectedCodexHomePath?: GetSelectedCodexHomePath,
|
||||
prepareClaudeAuth?: (
|
||||
target?: ClaudeAccountSelectionTarget
|
||||
) => Promise<ClaudeRuntimeAuthPreparation>,
|
||||
|
||||
Reference in New Issue
Block a user