Commit beaf2de
#2948/#3003 made static `readonly: true` fields server-enforced on UPDATE (a
non-system PATCH forging `approval_status: 'approved'` is silently stripped in
the engine), but INSERT was exempt. For approval/status/verdict columns that
exemption was the SHORTER attack: instead of the #3003 draft-then-PATCH move, a
non-system caller could POST a record already `approval_status: 'approved'` in
one step — and the UPDATE-only strip never reached it.
The strip now also runs on INSERT, but at the EXTERNAL data-write INGRESS
(DataProtocol.createData / createManyData / batchData / cloneData) rather than in
the engine. That seam is the single point every external programmatic create
funnels through — the REST CRUD route, the GraphQL/MCP dispatcher (bridge.create
→ callData → createData), and bulk import — while TRUSTED internal writers
(better-auth's adapter, the metadata repository, the seed loader) call
engine.insert directly and bypass it. Enforcing at the ingress protects every
caller/agent path at once without stripping the internal writers that
legitimately seed read-only columns on create (identity provisioning, provenance
stamps, event-log cursors) — the blast radius an engine-level insert strip would
have had.
- Caller-forged only: at the ingress the payload is raw caller input (owner/tenant
stamps land later inside engine.insert), so only keys the caller sent are
dropped.
- Re-derives the default: a stripped field falls back to its declared defaultValue
in the engine (a forged approval_status becomes `draft`, not NULL).
- System-context exempt; silent (HTTP 2xx); per-row on batch/import. readonlyWhen
stays INSERT-exempt (a conditional lock needs a prior record).
- Author-defined business objects only: platform objects (managedBy set, or the
sys_ namespace) carry their own field-write governance that a silent strip must
not pre-empt — ADR-0086 REJECTS (403) a forged managed_by/package_id on
sys_permission_set, #3004 rejects a forged owner_id; several of those columns
are readonly. The #3043 threat is app approval/status fields, never sys_ — the
same boundary applySystemFields uses for ownership.
Behavior change: a non-system create through the data API (REST / GraphQL / MCP /
import) can no longer seed a readonly column on an author-defined object. Flows
that legitimately write read-only columns at creation must run system-context,
the same requirement the UPDATE strip already imposes.
Proof: metadata-protocol protocol.readonly-insert.test.ts (forge stripped, default
re-derived, system exempt, batch per-row, platform objects deferred to their
guards) + the showcase-static-readonly dogfood test (insert-forge stripped e2e
over HTTP). Contract surfaces updated: field.zod readonly describe (+regenerated
reference), spec liveness note, authz-conformance matrix row.
Claude-Session: https://claude.ai/code/session_01P5fRY4ctobCeFpBba1oGrz
Co-authored-by: Claude <noreply@anthropic.com>
1 parent e057f42 commit beaf2de
8 files changed
Lines changed: 290 additions & 32 deletions
File tree
- .changeset
- content/docs/references/data
- packages
- metadata-protocol/src
- qa/dogfood/test
- spec
- liveness
- src/data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
153 | | - | |
| 153 | + | |
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| |||
Lines changed: 117 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
446 | 446 | | |
447 | 447 | | |
448 | 448 | | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
449 | 504 | | |
450 | 505 | | |
451 | 506 | | |
| |||
2685 | 2740 | | |
2686 | 2741 | | |
2687 | 2742 | | |
| 2743 | + | |
| 2744 | + | |
| 2745 | + | |
| 2746 | + | |
| 2747 | + | |
| 2748 | + | |
| 2749 | + | |
2688 | 2750 | | |
2689 | 2751 | | |
2690 | | - | |
| 2752 | + | |
2691 | 2753 | | |
2692 | 2754 | | |
2693 | 2755 | | |
| |||
2765 | 2827 | | |
2766 | 2828 | | |
2767 | 2829 | | |
2768 | | - | |
| 2830 | + | |
| 2831 | + | |
| 2832 | + | |
| 2833 | + | |
| 2834 | + | |
| 2835 | + | |
| 2836 | + | |
2769 | 2837 | | |
2770 | 2838 | | |
2771 | 2839 | | |
| |||
3101 | 3169 | | |
3102 | 3170 | | |
3103 | 3171 | | |
| 3172 | + | |
| 3173 | + | |
| 3174 | + | |
| 3175 | + | |
3104 | 3176 | | |
3105 | 3177 | | |
3106 | 3178 | | |
3107 | 3179 | | |
3108 | | - | |
| 3180 | + | |
3109 | 3181 | | |
3110 | 3182 | | |
3111 | 3183 | | |
| |||
3175 | 3247 | | |
3176 | 3248 | | |
3177 | 3249 | | |
| 3250 | + | |
| 3251 | + | |
| 3252 | + | |
| 3253 | + | |
| 3254 | + | |
| 3255 | + | |
| 3256 | + | |
3178 | 3257 | | |
3179 | 3258 | | |
3180 | | - | |
| 3259 | + | |
3181 | 3260 | | |
3182 | 3261 | | |
3183 | 3262 | | |
| |||
0 commit comments