fix: prevent crashes from corrupted JSON files in storage

- Add null byte/control character detection in storage read/update
- Wrap JSON.parse in try-catch with descriptive error messages
- Make stats command resilient to corrupted files by catching errors
- Fixes 'JSON Parse error: Unterminated string' crashes in stats command
This commit is contained in:
Cole Leavitt
2026-01-14 17:49:32 -07:00
parent 23848ed326
commit 061652ed31
2 changed files with 26 additions and 8 deletions
+2 -2
View File
@@ -86,13 +86,13 @@ async function getAllSessions(): Promise<Session.Info[]> {
const sessions: Session.Info[] = []
const projectKeys = await Storage.list(["project"])
const projects = await Promise.all(projectKeys.map((key) => Storage.read<Project.Info>(key)))
const projects = await Promise.all(projectKeys.map((key) => Storage.read<Project.Info>(key).catch(() => undefined)))
for (const project of projects) {
if (!project) continue
const sessionKeys = await Storage.list(["session", project.id])
const projectSessions = await Promise.all(sessionKeys.map((key) => Storage.read<Session.Info>(key)))
const projectSessions = await Promise.all(sessionKeys.map((key) => Storage.read<Session.Info>(key).catch(() => undefined)))
for (const session of projectSessions) {
if (session) {
+24 -6
View File
@@ -174,8 +174,17 @@ export namespace Storage {
if (!content.trim()) {
throw new NotFoundError({ message: `Empty file: ${target}` })
}
const result = JSON.parse(content)
return result as T
const hasControlCharacters = /[\x00-\x08\x0B\x0C\x0E-\x1F]/.test(content)
if (hasControlCharacters) {
throw new NotFoundError({ message: `Corrupted file detected: ${target}` })
}
try {
const result = JSON.parse(content)
return result as T
} catch (e) {
const message = e instanceof Error ? e.message : String(e)
throw new NotFoundError({ message: `Failed to parse JSON from ${target}: ${message}` })
}
})
}
@@ -188,10 +197,19 @@ export namespace Storage {
if (!content.trim()) {
throw new NotFoundError({ message: `Empty file: ${target}` })
}
const parsed = JSON.parse(content)
fn(parsed)
await Bun.write(target, JSON.stringify(parsed, null, 2))
return parsed as T
const hasControlCharacters = /[\x00-\x08\x0B\x0C\x0E-\x1F]/.test(content)
if (hasControlCharacters) {
throw new NotFoundError({ message: `Corrupted file detected: ${target}` })
}
try {
const parsed = JSON.parse(content)
fn(parsed)
await Bun.write(target, JSON.stringify(parsed, null, 2))
return parsed as T
} catch (e) {
const message = e instanceof Error ? e.message : String(e)
throw new NotFoundError({ message: `Failed to parse JSON from ${target}: ${message}` })
}
})
}