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)

Open
#1,470 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.