mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
getSafeRelativePath strips leading `/` and `\` before testing absoluteness, so the only rooted spelling that can still reach the guard is a Windows drive designator. It tested that with the host `path.isAbsolute`, which left two gaps: the drive-absolute form `C:/payload` was refused on Windows but admitted as an ordinary relative filename on macOS/Linux, and the drive-RELATIVE form `C:payload` was admitted on every host including Windows, where `win32.resolve(root, 'C:payload')` discards the worktree root and lands under C:'s current directory. That holds for a drive root (`D:\wt`) and for the `\\wsl.localhost\<Distro>\...` UNC root a WSL project uses, both verified. The value reaches the guard from two configs: the per-user Worktree Shared Paths setting alone on the create path (createWorktreeLinkedPaths, called with `repo.symlinkPaths` from orca-runtime.ts:27820 and worktree-remote.ts:2636), and that setting merged with the repo's checked-in `orca.yaml` `worktree.sharedDirectories` on the removal and detection paths (getWorktreeSharedLinkPaths). No repo config is required to reach it. Replace both `isAbsolute` calls with a `/^[a-zA-Z]:/` test, verified by fuzz to be a strict superset of `posix.isAbsolute || win32.isAbsolute` for every post-strip input. No filesystem escape is closed on macOS/Linux, where such an entry resolves to a literal in-worktree filename. Cost: on POSIX, `:` is a legal filename character, so a shared/linked path whose first segment is `<letter>:...` is now refused where it previously worked — it stops being created, and if a worktree already holds an Orca-created symlink there it stops being excluded from the untracked-file filters in all four findExistingWorktreeSymlinkPaths callers, which means a refused non-force worktree removal (remove-registered-local-worktree.ts:91, orca-runtime.ts:30205), a phantom untracked row in Source Control (status-read.ts:90), and a blocked hosted-review creation (hosted-review-creation-git-state.ts:290) — and removeWorktreeLinkedPaths no longer unlinks it, so nothing cleans it up. Accepted because a per-host verdict would defeat the point of judging the same config identically on every host it is evaluated on. Co-authored-by: Neil <neil@example.com>
84 lines
3.9 KiB
TypeScript
84 lines
3.9 KiB
TypeScript
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
import { findExistingWorktreeSymlinkPaths, getSafeRelativePath } from './worktree-symlink-detection'
|
|
|
|
describe('getSafeRelativePath', () => {
|
|
// The only case in this file that binds the production change: every one of
|
|
// these is admitted by at least one host's `path.isAbsolute` — the
|
|
// drive-relative spellings are admitted by *every* host's, Windows included.
|
|
it('rejects Windows drive-qualified entries, including drive-relative, on any host', () => {
|
|
expect(getSafeRelativePath('C:\\Windows\\Temp')).toEqual({ safe: false })
|
|
expect(getSafeRelativePath('C:/Windows/Temp')).toEqual({ safe: false })
|
|
expect(getSafeRelativePath(' d:\\payload ')).toEqual({ safe: false })
|
|
// Drive-RELATIVE: `win32.resolve('D:\\wt', 'C:payload')` discards the
|
|
// worktree root and lands under C:'s current directory.
|
|
expect(getSafeRelativePath('C:payload')).toEqual({ safe: false })
|
|
expect(getSafeRelativePath('c:')).toEqual({ safe: false })
|
|
})
|
|
|
|
it('keeps colon-bearing paths that are not drive-qualified', () => {
|
|
expect(getSafeRelativePath('build:out')).toEqual({ safe: true, rel: 'build:out' })
|
|
expect(getSafeRelativePath('logs/2026-08-31T10:00.txt')).toEqual({
|
|
safe: true,
|
|
rel: 'logs/2026-08-31T10:00.txt'
|
|
})
|
|
})
|
|
|
|
// These pin the rest of the guard expression, which this commit rewrote:
|
|
// both `isAbsolute` calls were deleted as provably subsumed by the strip
|
|
// above plus the drive check, so their inputs must still be judged the same.
|
|
it('still strips leading separators of either flavour and keeps the remainder relative', () => {
|
|
expect(getSafeRelativePath('node_modules')).toEqual({ safe: true, rel: 'node_modules' })
|
|
expect(getSafeRelativePath(' .env ')).toEqual({ safe: true, rel: '.env' })
|
|
expect(getSafeRelativePath('/.env')).toEqual({ safe: true, rel: '.env' })
|
|
expect(getSafeRelativePath('\\.env')).toEqual({ safe: true, rel: '.env' })
|
|
expect(getSafeRelativePath('//srv/share/x')).toEqual({ safe: true, rel: 'srv/share/x' })
|
|
})
|
|
|
|
it('still rejects empty, whitespace-only, and parent-directory entries', () => {
|
|
expect(getSafeRelativePath('')).toEqual({ safe: false })
|
|
expect(getSafeRelativePath(' ')).toEqual({ safe: false })
|
|
expect(getSafeRelativePath('../secrets')).toEqual({ safe: false })
|
|
expect(getSafeRelativePath('safe/../../escape')).toEqual({ safe: false })
|
|
expect(getSafeRelativePath('..\\escape')).toEqual({ safe: false })
|
|
expect(getSafeRelativePath('foo\\..\\..\\escape')).toEqual({ safe: false })
|
|
})
|
|
})
|
|
|
|
describe('findExistingWorktreeSymlinkPaths', () => {
|
|
let root: string
|
|
let primary: string
|
|
let worktree: string
|
|
|
|
beforeEach(() => {
|
|
root = mkdtempSync(join(tmpdir(), 'orca-symlink-detection-'))
|
|
primary = join(root, 'primary')
|
|
worktree = join(root, 'worktree')
|
|
mkdirSync(primary)
|
|
mkdirSync(worktree)
|
|
})
|
|
|
|
afterEach(() => {
|
|
rmSync(root, { recursive: true, force: true })
|
|
})
|
|
|
|
// Why skipped on Windows rather than made platform-independent: the fixture
|
|
// needs a real on-disk directory named `C:`, which only POSIX allows. The
|
|
// Windows half of this guard is bound by the unit tests above, which run
|
|
// everywhere because they never touch the filesystem.
|
|
const posixIt = process.platform === 'win32' ? it.skip : it
|
|
|
|
posixIt('does not report a drive-qualified entry even when the literal path exists', async () => {
|
|
mkdirSync(join(worktree, 'C:'))
|
|
writeFileSync(join(primary, 'payload'), 'X=1\n')
|
|
symlinkSync(join(primary, 'payload'), join(worktree, 'C:', 'payload'))
|
|
symlinkSync(join(primary, 'payload'), join(worktree, 'C:payload'))
|
|
|
|
await expect(
|
|
findExistingWorktreeSymlinkPaths(worktree, ['C:/payload', 'C:\\payload', 'C:payload'])
|
|
).resolves.toEqual([])
|
|
})
|
|
})
|