mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
Orca CLI commands inside a sandbox that blocks chmod (e.g. Claude Code's Bash sandbox) no longer fail with a misleading "file is invalid": the pre-read hardening of existing secure files ignores EPERM/EACCES/EROFS; writers still create files 0600. Fixes #20937
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { chmodSync, mkdtempSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
||||
import type * as NodeFs from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
__getSecureFileHardeningCacheStateForTests,
|
||||
__resetSecureFileHardenedPathsForTests,
|
||||
@@ -15,6 +16,21 @@ import {
|
||||
} from './windows-security-addon'
|
||||
import { createFakeWindowsSecurity } from './windows-security-addon.test-fixture'
|
||||
|
||||
const chmodFailure = vi.hoisted((): { code: string | null } => ({ code: null }))
|
||||
|
||||
vi.mock('node:fs', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof NodeFs>()
|
||||
return {
|
||||
...actual,
|
||||
chmodSync: (...args: Parameters<typeof actual.chmodSync>) => {
|
||||
if (chmodFailure.code) {
|
||||
throw Object.assign(new Error(chmodFailure.code), { code: chmodFailure.code })
|
||||
}
|
||||
actual.chmodSync(...args)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const posixModeIt = process.platform === 'win32' ? it.skip : it
|
||||
|
||||
describe('POSIX secure files', () => {
|
||||
@@ -27,6 +43,7 @@ describe('POSIX secure files', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
chmodFailure.code = null
|
||||
resetWindowsSecurityLoadForTests()
|
||||
__resetSecureFileHardenedPathsForTests()
|
||||
setWindowsSecurityForTests(null)
|
||||
@@ -82,6 +99,22 @@ describe('POSIX secure files', () => {
|
||||
expect(statMode(userDataPath)).toBe(0o700)
|
||||
})
|
||||
|
||||
/** Claude Code's Bash sandbox denies chmod on userData; `orca worktree list` failed reading it (#20937). */
|
||||
posixModeIt(
|
||||
'lets readers proceed when a sandbox denies hardening, but not on other errors',
|
||||
() => {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' })
|
||||
const targetPath = join(tempDir(), 'secret.json')
|
||||
writeFileSync(targetPath, '{}')
|
||||
|
||||
chmodFailure.code = 'EPERM'
|
||||
expect(() => hardenExistingSecureFile(targetPath)).not.toThrow()
|
||||
|
||||
chmodFailure.code = 'EIO'
|
||||
expect(() => hardenExistingSecureFile(targetPath)).toThrow('EIO')
|
||||
}
|
||||
)
|
||||
|
||||
posixModeIt('LRU-bounds POSIX hardening entries while keeping recent paths cached', () => {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' })
|
||||
__resetSecureFileHardenedPathsForTests({
|
||||
|
||||
@@ -207,16 +207,28 @@ export function isUnreadableError(error: unknown): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
/** Readers tighten modes opportunistically; a sandbox that denies chmod must not fail the read (#20937). */
|
||||
export function hardenExistingSecureFile(targetPath: string): void {
|
||||
const dir = dirname(targetPath)
|
||||
if (existsSync(dir)) {
|
||||
hardenSecurePathOnce(dir, true)
|
||||
}
|
||||
if (existsSync(targetPath)) {
|
||||
hardenSecurePathOnce(targetPath, false)
|
||||
try {
|
||||
const dir = dirname(targetPath)
|
||||
if (existsSync(dir)) {
|
||||
hardenSecurePathOnce(dir, true)
|
||||
}
|
||||
if (existsSync(targetPath)) {
|
||||
hardenSecurePathOnce(targetPath, false)
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isPermissionDeniedError(error)) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPermissionDeniedError(error: unknown): boolean {
|
||||
const code = error instanceof Error && 'code' in error ? error.code : undefined
|
||||
return code === 'EPERM' || code === 'EACCES' || code === 'EROFS'
|
||||
}
|
||||
|
||||
/** Caches the current metadata snapshot for a just-hardened path, or clears it if the path is gone. */
|
||||
function rememberHardenedPath(targetPath: string, isDirectory: boolean): void {
|
||||
const entry = getHardenedPathCacheEntry(targetPath, isDirectory)
|
||||
|
||||
Reference in New Issue
Block a user