Skip to content

Undeclared fields still reach the driver: beforeInsert hooks run and an auto-number is consumed before the request is refused, and the whole INSERT with its values is logged at ERROR (17.0.0 GA) #8682

Description

@hotlong

Part of objectstack-ai/hotcrm#689 (never mirrored until now). Measured on @objectstack/*17.0.0 GA during HotCRM's GA close-out sweep (hotcrm#1150 / #1152), using that card's own reproduction verbatim.

The client-facing behaviour is correct and unchanged — a clean 400 INVALID_FIELD. This is about what happens behind it. Two halves, which have different states.

Half A — validation ordering: REPRODUCES

An unknown field is still rejected by the driver, not by schema validation, and everything upstream of the statement has already run.

POST /api/v1/data/crm_account {"name":"P689 probe","zzz_nonexistent_field":"x"}
-- 400 {"error":"Unknown field 'zzz_nonexistent_field' on object 'crm_account'","code":"INVALID_FIELD"}

server log:

ERROR Insert operation failed {"object":"crm_account","error":{"message":
"insert into `crm_account` (`account_number`, `created_at`, `created_by`, `id`, `is_active`,
`name`, `name_normalized`, `organization_id`, `owner_id`, `territory`, `tier`, `type`,
`updated_at`, `updated_by`, `zzz_nonexistent_field`) values ('ACC-000011',
'2026-08-14T13:08:49.769Z', 'DWci...', 'q-UpNH4tqX3n3wKP', true, 'P689 probe ...',
'p689 probe ...', 'org_mssy5q4o0pc5iqui', ...) returning *
- table crm_account has no column named zzz_nonexistent_field"

An id, an auto-number, a normalized name, owner/creator resolution and three column defaults were all produced for a request that was about to be refused.

Hooks run too.crm_forecast uses nameField: 'display_title', so name is undeclared there:

insert into `crm_forecast` (..., `period`, `period_end`, `period_label`, `period_start`, ...)
values (..., 'quarter', '2026-09-30', 'Q3 2026', '2026-07-01', ...)
- table crm_forecast has no column named name

period_label = 'Q3 2026' was never sent by the caller; it is derived by the app's beforeInsert hook. The hook executed on a request that was then rejected.

Independent of the log, the auto-number sequence shows the same thing — this observable does not depend on log contents at all:

valid create before the bad request : ACC-000013
the bad request : 400 INVALID_FIELD
valid create after the bad request : ACC-000015
-- gap across the rejected request : 1 (the refused request consumed ACC-000014)

Half B — value logging: PARTLY fixed, the exposure survives

sub-claim on hotcrm#689GA state
the entry appears as [REST] Unhandled error, a false unhandled signalFIXED — zero [REST] Unhandled error lines carry a SqliteError or no column named in a full run; the entry is now a single handled ERROR Insert operation failed
the full INSERT with every value is written at ERROR levelREPRODUCES
each entry duplicates the whole statement twice (message + stack)REPRODUCES — the stack field opens SqliteError: insert into ... with the statement again

Confirmed with planted canaries: a request carrying description: 'DESCRIPTION-VALUE-CANARY' and a misspelled zzz_secret_field: 'SENSITIVE-CANARY-9f3a2b' put both literals into the log, alongside the organization id and the acting user id. A single mistyped field name in a client request is still enough to write an entire row's values to disk at ERROR level.

Suggested direction

Reject unknown fields against the object schema before hooks and before statement construction; and when a driver-level error does reach the logger, log the failing column and the object rather than the bound statement.

Evidence: hotcrm#1152 probe 50-689.mjs; readings posted on hotcrm#689.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions