fix(ai-vault): reject invalid Cursor sidecar UTF-8

This commit is contained in:
Brennan Benson
2026-08-10 15:10:40 -07:00
parent 70e9548c6c
commit 1318cdac80
2 changed files with 31 additions and 1 deletions
+24
View File
@@ -289,6 +289,30 @@ describe('scanCursorSidecars', () => {
)
})
it('isolates invalid UTF-8 metadata without rejecting other sidecars', async () => {
const root = await createRoot()
const chatsRoot = join(root, 'chats')
const bucket = 'abababababababababababababababab'
const validContent = JSON.stringify({ createdAtMs: 10, title: 'valid' })
await Promise.all([
addSession(chatsRoot, bucket, 'aaa-invalid'),
addSession(chatsRoot, bucket, 'zzz-valid', validContent)
])
const invalidPath = join(chatsRoot, bucket, 'aaa-invalid', 'meta.json')
await writeFile(invalidPath, Buffer.alloc(100_000, 0x80))
const result = await scanCursorSidecars(
defaultCursorSidecarScanRequest(chatsRoot, [], process.platform),
context
)
expect(result.sidecars.map((sidecar) => sidecar.sessionId)).toEqual(['zzz-valid'])
expect(result.counters.returnedBytes).toBe(Buffer.byteLength(validContent))
expect(result.issues).toContainEqual(
expect.objectContaining({ path: invalidPath, message: 'invalid_utf8' })
)
})
it('normalizes scope truncation deterministically inside the owning-host scan', async () => {
const root = await createRoot()
const chatsRoot = join(root, 'chats')
@@ -5,6 +5,7 @@ import type { FileHandle } from 'node:fs/promises'
import { dirname, isAbsolute, join, relative, sep } from 'node:path'
const OPEN_NOFOLLOW = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0
const STRICT_UTF8_DECODER = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true })
export type VerifiedBoundedTextFileOptions = {
maxBytes: number
@@ -28,7 +29,12 @@ export async function readVerifiedBoundedTextFile(
if (!opened.isFile() || !sameFileIdentity(beforeOpen, opened)) {
throw new Error('verified_file_changed')
}
return (await readBoundedFileHandle(handle, maxBytes)).toString('utf8')
const bytes = await readBoundedFileHandle(handle, maxBytes)
try {
return STRICT_UTF8_DECODER.decode(bytes)
} catch {
throw new Error('invalid_utf8')
}
} finally {
await handle.close()
}