Severity: low — cosmetic + minor information disclosure. Same family as UB-002 (unguarded property access on req.body).
The bug
dist/esm/src/OverlayExpress.js:1273:
this.app.post('/admin/health-check', checkAdminAuth, (req, res) => {
(async () => {
try {
const { url } = req.body // ← line 1277 — throws when req.body is undefined
// …
} catch (error) {
return res.status(400).json({
status: 'error',
message: error instanceof Error ? error.message : 'An unknown error occurred'
})
}
})()
})
With a missing/empty JSON body, req.body is undefined. const { url } = undefined throws:
TypeError: Cannot destructure property 'url' of 'req.body' as it is undefined.
The catch forwards error.message verbatim. Status is 400 (correct), but:
{
"status": "error",
"message": "Cannot destructure property 'url' of 'req.body' as it is undefined."
}
Impact
- Caller sees a raw V8 destructuring-syntax error instead of a clean "missing url" description.
- Minor info disclosure: leaks the fact that a destructure is used internally, the expected property name, and the parameter reference path.
- Not a security bypass. Pure quality issue.
Reproducer
TOKEN="<your-admin-token>"
curl -s -X POST 'http://localhost:8080/admin/health-check' \
-H "Authorization: Bearer $TOKEN"
# → 400
# {"status":"error","message":"Cannot destructure property 'url' of 'req.body' as it is undefined."}
Patch
Guard before destructuring:
this.app.post('/admin/health-check', checkAdminAuth, (req, res) => {
(async () => {
try {
- const { url } = req.body
+ if (!req.body || typeof req.body !== 'object') {
+ throw new Error('Missing request body — expected JSON with a `url` field')
+ }
+ const { url } = req.body
+ if (typeof url !== 'string' || url.length === 0) {
+ throw new Error('Invalid request: `url` must be a non-empty string')
+ }
Test:
it('returns a clean 400 when the body is missing', async () => {
const res = await request(app).post('/admin/health-check').set('Authorization', `Bearer ${token}`)
expect(res.status).toBe(400)
expect(res.body.message).toMatch(/url|body/i)
expect(res.body.message).not.toMatch(/Cannot destructure|as it is undefined/)
})
Related
Same root cause as UB-002 (#16) — POST /submit has the same missing-body guard issue
(Array.from(req.body) → TypeError: undefined is not iterable). Both are
the same refactor: guard the body before unpacking it.
How this was found
Differential parity harness between @bsv/overlay-express@2.2.0 and a Rust port. rust-overlay's body deserializer returns "Invalid body: failed to get JSON for body value"; mainline's V8 error leaked through. Corpus entry admin/admin_health_check_authed.json pins both responses.
Happy to open a PR.
Severity: low — cosmetic + minor information disclosure. Same family as UB-002 (unguarded property access on
req.body).The bug
dist/esm/src/OverlayExpress.js:1273:With a missing/empty JSON body,
req.bodyisundefined.const { url } = undefinedthrows:The
catchforwardserror.messageverbatim. Status is 400 (correct), but:{ "status": "error", "message": "Cannot destructure property 'url' of 'req.body' as it is undefined." }Impact
Reproducer
Patch
Guard before destructuring:
this.app.post('/admin/health-check', checkAdminAuth, (req, res) => { (async () => { try { - const { url } = req.body + if (!req.body || typeof req.body !== 'object') { + throw new Error('Missing request body — expected JSON with a `url` field') + } + const { url } = req.body + if (typeof url !== 'string' || url.length === 0) { + throw new Error('Invalid request: `url` must be a non-empty string') + }Test:
Related
Same root cause as UB-002 (#16) —
POST /submithas the same missing-body guard issue(
Array.from(req.body)→TypeError: undefined is not iterable). Both arethe same refactor: guard the body before unpacking it.
How this was found
Differential parity harness between
@bsv/overlay-express@2.2.0and a Rust port. rust-overlay's body deserializer returns"Invalid body: failed to get JSON for body value"; mainline's V8 error leaked through. Corpus entryadmin/admin_health_check_authed.jsonpins both responses.Happy to open a PR.