test: verify error responses never leak stack traces or file paths
Add tests confirming production error handler sanitizes all internal errors (ENOENT, stack traces, file paths). Add static analysis test verifying no route file passes err.stack to c.json() responses. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
21bb46c1b0
commit
2fea2bf8c9
@@ -49,4 +49,49 @@ describe('global error handler', () => {
|
||||
const res = await app.request('/test')
|
||||
expect(res.status).toBe(500)
|
||||
})
|
||||
|
||||
it('production error responses never contain stack traces or file paths', async () => {
|
||||
const originalEnv = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
const dangerousMessages = [
|
||||
'ENOENT: no such file or directory, open /Users/dorian/Projects/botfights/server/data/db.sqlite',
|
||||
'Error at Object.<anonymous> (/app/server/src/engine/orchestrator.ts:42:15)',
|
||||
'ReferenceError: x is not defined\n at /app/server/src/routes/fights.ts:100:5',
|
||||
]
|
||||
|
||||
for (const message of dangerousMessages) {
|
||||
const app = new Hono()
|
||||
app.onError((_err, c) => {
|
||||
const msg = process.env.NODE_ENV === 'production' ? 'Internal server error' : _err.message
|
||||
return c.json({ error: msg }, 500)
|
||||
})
|
||||
app.get('/test', () => { throw new Error(message) })
|
||||
const res = await app.request('/test')
|
||||
const body = await res.json() as { error: string }
|
||||
expect(body.error).toBe('Internal server error')
|
||||
expect(body.error).not.toContain('.ts')
|
||||
expect(body.error).not.toContain('.js')
|
||||
expect(body.error).not.toContain('/src/')
|
||||
expect(body.error).not.toContain('at ')
|
||||
expect(body.error).not.toContain('ENOENT')
|
||||
}
|
||||
|
||||
process.env.NODE_ENV = originalEnv
|
||||
})
|
||||
|
||||
it('no route file contains err.stack in JSON response', async () => {
|
||||
// Static analysis: verify no route file passes stack traces to c.json()
|
||||
const fs = await import('fs')
|
||||
const path = await import('path')
|
||||
const routesDir = path.resolve(import.meta.dirname || '.', '.')
|
||||
const files = fs.readdirSync(routesDir).filter(f => f.endsWith('.ts') && !f.includes('.test.'))
|
||||
|
||||
for (const file of files) {
|
||||
const content = fs.readFileSync(path.join(routesDir, file), 'utf-8')
|
||||
// err.stack should never appear in c.json() calls
|
||||
expect(content).not.toMatch(/c\.json\([^)]*err\.stack/)
|
||||
expect(content).not.toMatch(/c\.json\([^)]*error\.stack/)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user