HarperFast / HarperFast/harper

null / empty-string / missing primary key is silently accepted and stored (REST/ops/SQL)

Open
#1,469 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

A create whose primary key resolves to `null`, empty-string `""`, or missing is **silently accepted and committed** — across REST POST, ops `insert`, and SQL `INSERT` — with no validation rejection. The result is phantom rows that collide on the null key, can't be addressed by a normal PK GET, and pollute scans.

Confirmed on **main `541a3d33d` (v5.1.11)**.

## Reproduction

```
npm run test:integration -- "integrationTests/qa-scratch/qa228-empty-payloads.test.ts"
```

Observed (v5.1.11):

| Surface | Input | Result |
|---|---|---|
| REST POST | `{id: null}` | **201**, stored `id: null` |
| REST POST | `{id: ""}` | **201**, stored `id: ""` |
| ops `insert` | `[{id: null}]` | **200**, stored `id: null` |
| ops `insert` | `[{ }]` (no id) | **200**, stored (auto-key / null) |
| SQL `INSERT` | omit PK column | **200**, accepted |

The record is actually committed (this extends D-041, which had observed only the response-side null); a subsequent read/scan returns the phantom null/empty-PK rows.

## Defensible companions (NOT bugs)

For contrast, these are correct: `{}` on REST PUT uses the URL-path PK; `[]` via ops insert is a clean no-op (200, nothing stored); a `/Widget/null` path segment stores the **string** `"null"` (correct — it's a string key).

## Impact

Medium — silent-wrong data. A pipeline ingesting third-party JSON with a missing/null `id` silently accumulates junk rows instead of getting a clean 400. Multiple `id:null` creates collide.

## Suggested fix

Reject any create whose PK resolves to `null` / `undefined` / `""` with a clean **400** on all surfaces (REST / ops / SQL).

---
*Surfaced by the QA-explorer campaign (scenario QA-228), re-confirmed on `main` v5.1.11. Severity: medium. Related: the same sweep co-surfaced REST-write 500s on malformed/non-object bodies (filed separately). Filed for @kris.*

— Claude (Opus 4.8, 1M)

Contributor guide

Open the contributing guide

Research direction

Run integrationTests/qa-scratch/qa228-empty-payloads.test.ts and compare the REST POST, ops insert, and SQL INSERT paths described in the issue. Trace how each surface resolves the primary key; done means null, undefined, empty-string, and missing keys are rejected with a clean 400 without storing rows, while the listed companion cases remain valid.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nodejs, sql
Domain
api, backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.