HarperFast / HarperFast/harper
REST write path: malformed/non-object/unsupported request bodies return 500 instead of 4xx; unsupported Content-Type stored as blob (no 415)
- Dominant language
- JavaScript
- Stars
- 89
- Forks
- 10
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 200
Description
## Summary
The REST write path returns **HTTP 500 instead of a 4xx** for several classes of malformed / unsupported / non-object request bodies, and **silently stores** an unsupported `Content-Type` as an opaque blob instead of returning 415. The ops API handles all of these cleanly with 400s — the gap is REST-specific. This is a concrete instantiation of the systemic 4xx-as-500 pattern tracked in #1299.
Confirmed on **main `541a3d33d` (v5.1.11)**.
## Reproduction
```
npm run test:integration -- "integrationTests/qa-scratch/qa225-content-negotiation.test.ts"
npm run test:integration -- "integrationTests/qa-scratch/qa226-malformed-poison.test.ts"
```
## Defects (all REST; ops returns clean 400/406 for the same inputs)
**1. Malformed / Content-Type-mismatched CBOR/msgpack body → 500 (expected 400).**
- JSON body + `Content-Type: application/cbor` → 500 ("…arrays, maps, or strings with length over 4294967295")
- msgpack garbage (`0xc1×3`) + `application/x-msgpack` → 500 ("Data read, but end of buffer not reached")
- CBOR garbage (`0xff×4`) + `application/cbor` → 500
- Locus: the Fastify `registerContentHandlers` parser catches + sets 400, but the **native-HTTP-worker `getDeserializer` path does not**, so decode errors propagate as 500.
**2. `text/plain` PUT to a record → 500.** The `text/plain` deserializer stores a bare string; the REST layer then can't extract `id` → `TypeError: Cannot read properties of undefined (reading 'id')`.
**3. Non-object JSON body → 500.** REST PUT/POST whose body JSON-parses to a non-object — empty string, whitespace/newline, bare scalar (`null`, `42`, `"x"`, `true`), or `[]` — → 500 `Cannot read properties of undefined (reading 'id')` at `validate()` (`Table.ts`, reads `.id` unguarded). Ops API returns a clean 400 for all of these.
**4. Unsupported `Content-Type` stored as opaque blob — no 415.** `application/xml` and `text/csv` request bodies return **204** and are stored as `{contentType, data, id}` wrappers rather than rejected with 415. Silent accept-as-blob.
## What works (scope)
The READ matrix is mostly correct: JSON/CBOR/msgpack/CSV serialize faithfully; an unsupported `Accept` returns a clean **406** (not 500); q-value negotiation picks correctly. These are not part of this issue.
## Related / not included here
- A malformed-body **scan-poison** was hypothesized but **refuted on main** (QA-226): every decode-failing write stores nothing, and xml/csv store a plain-object wrapper that freezes fine. The `Object.freeze` scan-wedge stays exclusive to the deliberate octet-stream sub-attribute path — that's #1298, not this.
- The `Accept: text/plain` sub-attribute **GET** → 500 (`contentTypes.ts` `data.toString()` null-deref) belongs to the read-path hardening in #1300 (F-010/F-011 family).
## Suggested fix
Route the native-HTTP `getDeserializer` decode errors through the same `catch → 400` the Fastify path uses; guard non-object bodies → 400; fix the `text/plain` record-PUT id-extraction; and return **415** for unsupported request Content-Types instead of storing them.
---
*Surfaced by the QA-explorer campaign (scenarios QA-225/QA-226/QA-228), re-confirmed on `main` v5.1.11. Severity: medium (wrong status / silent accept; no data loss). Part of the #1299 4xx-as-500 META. Filed for @kris.*
— Claude (Opus 4.8, 1M)
Contributor guide
Research direction
Run the two named integration tests first, then trace the REST native-HTTP worker's getDeserializer path and compare it with registerContentHandlers' error handling. Inspect validate() in Table.ts and contentTypes.ts for the non-object, text/plain, and unsupported Content-Type cases. Done means the listed REST writes return 400 or 415 as specified, malformed writes store nothing, and the integration tests pass without changing the excluded read-path cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nodejs, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100