HarperFast / HarperFast/harper
Custom resource `get()` / constructor error messages leak in HTTP 500 response body
- Dominant language
- JavaScript
- Stars
- 89
- Forks
- 10
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 200
Description
## Summary
When a custom resource's `get()` method or `constructor()` throws, the error's `.message` string appears verbatim in the JSON HTTP 500 response body's `title` field.
## Symptom
```js
class Secrets extends tables.Secrets {
get() {
throw new Error('DB_URL=postgres://user:s3cr3t@internal-db.corp:5432/prod')
}
}
```
Response body (HTTP 500):
```json
{
"type": "error:Error",
"code": "Error",
"title": "DB_URL=postgres://user:s3cr3t@internal-db.corp:5432/prod"
}
```
The full `.message` is returned to the caller. The `code` and `type` fields also expose the error class name.
## Additional behaviors observed
- Sync throw and rejected Promise are serialized identically (both leak the message).
- `constructor()` throw leaks the same way; the server survives and serves subsequent requests normally.
- Non-Error throws (`throw 'some string'`) surface the string value in `title`.
- `throw new Response(...)` is not recognized as a short-circuit idiom — produces `title: "[object Response]"` (no leak, but also not useful).
- No unhandled-promise-rejections observed — all paths are caught; the leak is only in the serialization.
## Impact
In production, application-layer errors can expose internal implementation details through the API: database connection strings, internal hostnames, secret values embedded in error messages, library internals. The HTTP 500 status code is correct; the information leakage is in the body.
## Recommendation
Scrub `error.message` from the response body in non-development environments; substitute a generic message (e.g. `"Internal server error"`) and log the full error internally. Consider an opt-in `ClientError` subclass whose `.message` is intentionally user-visible, so developers can explicitly choose when to expose error text to callers.
## Environment
Harper: `7aaa5a152` (byte-identical to `main` @ `6797f091d`, reproduces on main).
Repro: `npm run test:integration -- "integrationTests/qa-scratch/qa169-custom-resource-errors.test.ts"` (cells S2, S3, S4)
---
*Surfaced by QA-explorer (QA-169). Filed by Claude (Sonnet 4.6) for @kriszyp.*
Contributor guide
Research direction
Run npm run test:integration -- "integrationTests/qa-scratch/qa169-custom-resource-errors.test.ts" and inspect cells S2, S3, and S4. Trace how custom-resource get() and constructor() failures are serialized for sync throws, rejected Promises, and non-Error values. Done means production HTTP 500 responses no longer expose error messages or class details while the existing status behavior remains covered by tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js, typescript
- Domain
- api, backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 46/100