mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
fix: validate clipboard staging permissions
This commit is contained in:
@@ -200,10 +200,10 @@ describe('registerClipboardHandlers', () => {
|
||||
handleMock.mockReset()
|
||||
spawnMock.mockClear()
|
||||
childStdinEndMock.mockClear()
|
||||
resolveAuthorizedPathMock.mockReset()
|
||||
resolveAuthorizedPathMock.mockImplementation(async (path: string) => path)
|
||||
resolveAuthorizedPathMock.mockReset().mockImplementation(async (path: string) => path)
|
||||
fsAccessMock.mockReset().mockResolvedValue(undefined)
|
||||
fsLstatMock.mockReset().mockResolvedValue({
|
||||
mode: 0o700,
|
||||
uid: typeof process.getuid === 'function' ? process.getuid() : 0,
|
||||
isDirectory: () => true,
|
||||
isSymbolicLink: () => false
|
||||
@@ -215,12 +215,10 @@ describe('registerClipboardHandlers', () => {
|
||||
async *[Symbol.asyncIterator]() {},
|
||||
close: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
fsRmMock.mockReset()
|
||||
fsRmMock.mockResolvedValue(undefined)
|
||||
fsRmMock.mockReset().mockResolvedValue(undefined)
|
||||
fsWriteFileMock.mockReset()
|
||||
fsOpenMock.mockReset()
|
||||
fsStatMock.mockReset()
|
||||
fsStatMock.mockResolvedValue({})
|
||||
fsStatMock.mockReset().mockResolvedValue({})
|
||||
clipboardReadTextMock.mockReset()
|
||||
clipboardReadBufferMock.mockReset()
|
||||
clipboardReadBufferMock.mockReturnValue(Buffer.alloc(0))
|
||||
|
||||
@@ -74,6 +74,7 @@ describe('cleanupExpiredRemoteClipboardFiles', () => {
|
||||
vi.clearAllMocks()
|
||||
accessMock.mockResolvedValue(undefined)
|
||||
lstatMock.mockResolvedValue({
|
||||
mode: 0o700,
|
||||
uid: typeof process.getuid === 'function' ? process.getuid() : 0,
|
||||
isDirectory: () => true,
|
||||
isSymbolicLink: () => false
|
||||
@@ -92,6 +93,7 @@ describe('cleanupExpiredRemoteClipboardFiles', () => {
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('scans only the owned staging root after legacy migration', async () => {
|
||||
@@ -161,6 +163,7 @@ describe('cleanupExpiredRemoteClipboardFiles', () => {
|
||||
|
||||
it('rejects an unsafe staging-root symlink before scanning it', async () => {
|
||||
lstatMock.mockResolvedValue({
|
||||
mode: 0o700,
|
||||
uid: typeof process.getuid === 'function' ? process.getuid() : 0,
|
||||
isDirectory: () => false,
|
||||
isSymbolicLink: () => true
|
||||
@@ -177,6 +180,7 @@ describe('cleanupExpiredRemoteClipboardFiles', () => {
|
||||
return
|
||||
}
|
||||
lstatMock.mockResolvedValue({
|
||||
mode: 0o700,
|
||||
uid: process.getuid() + 1,
|
||||
isDirectory: () => true,
|
||||
isSymbolicLink: () => false
|
||||
@@ -187,6 +191,23 @@ describe('cleanupExpiredRemoteClipboardFiles', () => {
|
||||
expect(opendirMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('rejects a POSIX staging root with insecure permissions', async () => {
|
||||
const processWithUid = Object.assign(Object.create(process) as NodeJS.Process, {
|
||||
getuid: () => 1000
|
||||
})
|
||||
vi.stubGlobal('process', processWithUid)
|
||||
lstatMock.mockResolvedValue({
|
||||
mode: 0o755,
|
||||
uid: 1000,
|
||||
isDirectory: () => true,
|
||||
isSymbolicLink: () => false
|
||||
})
|
||||
|
||||
await cleanupExpiredRemoteClipboardFiles(NOW_MS)
|
||||
|
||||
expect(opendirMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('migrates legacy directories once and records completion', async () => {
|
||||
accessMock.mockRejectedValue(new Error('ENOENT'))
|
||||
opendirMock.mockImplementation(async (targetPath: string) => {
|
||||
|
||||
@@ -99,7 +99,8 @@ async function ensureRemoteClipboardStagingRoot(): Promise<string> {
|
||||
await mkdir(stagingRoot, { recursive: true, mode: 0o700 })
|
||||
const rootStats = await lstat(stagingRoot)
|
||||
const wrongOwner = typeof process.getuid === 'function' && rootStats.uid !== process.getuid()
|
||||
if (!rootStats.isDirectory() || rootStats.isSymbolicLink() || wrongOwner) {
|
||||
const wrongMode = typeof process.getuid === 'function' && (rootStats.mode & 0o777) !== 0o700
|
||||
if (!rootStats.isDirectory() || rootStats.isSymbolicLink() || wrongOwner || wrongMode) {
|
||||
throw new Error('Remote clipboard staging root is unsafe')
|
||||
}
|
||||
return stagingRoot
|
||||
|
||||
Reference in New Issue
Block a user