Skip to content

POST /admin/health-check with empty body leaks a destructuring TypeError as the response message #69

Description

@Calgooon

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions