You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The metadata-liveness audit found object `enable.apiEnabled`/`apiMethods` parsed
but NOT enforced by REST — an object could not be hidden from the automatic API
despite the flag, a false sense of security. This wires them into the REST data
surface (the external API boundary only — internal objectql/hooks/flows unaffected):
- new RestServer.enforceApiAccess() loads object metadata and, per request:
- enable.apiEnabled === false → 404 OBJECT_API_DISABLED (existence not revealed)
- enable.apiMethods (non-empty whitelist) → unlisted op → 405 OBJECT_API_METHOD_NOT_ALLOWED
- called across all 12 data entry points: list/get/create/query/update/delete/
import/export/batch/createMany/updateMany/deleteMany.
- default-allow: no `enable` block, or apiEnabled unset/true + no apiMethods → no
change (no regression). Unknown objects fall through to the normal 404. A
metadata-read failure does not block (the data call needs the same metadata).
8 new tests (apiEnabled 404 on read+write, apiMethods 405/allow, no-regression
default, unknown-object pass-through, bulk path). Full rest suite green (110).
Closes the apiEnabled gap from object liveness; the gate now classifies
enable.apiEnabled/apiMethods live (evidence: rest-server.ts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(rest): enforce object-level API exposure (`enable.apiEnabled` / `enable.apiMethods`) on the REST data surface (ADR-0049 #1889). Previously these flags were parsed but unenforced — an object could not be hidden from the automatic API, a false sense of security. Now: `apiEnabled: false` → the object's `/api/v1/data/{object}` routes return 404 (existence not revealed); a non-empty `apiMethods` whitelist → operations outside it return 405. Enforced across list/get/create/query/update/delete/import/export/batch/createMany/updateMany/deleteMany. Default-allow (objects with no `enable` block, or `apiEnabled` unset/true and no `apiMethods`) behave exactly as before — no regression. This is the *external* API boundary only; internal callers (hooks, flows, objectql) are unaffected.
"enable": { "status": "dead", "evidence": "ObjectCapabilities block (trackHistory/searchable/apiEnabled/apiMethods/files/feeds/activities/trash/mru/clone) — all 10 flags have zero behavior-changing readers", "note": "apiEnabled/apiMethods NOT enforced by REST — false sense of security (ADR-0049-class)." },
35
+
"enable": {
36
+
"children": {
37
+
"apiEnabled": { "status": "live", "evidence": "packages/rest/src/rest-server.ts", "note": "enforced by enforceApiAccess on the REST data surface (ADR-0049 #1889) — apiEnabled:false → 404." },
38
+
"apiMethods": { "status": "live", "evidence": "packages/rest/src/rest-server.ts", "note": "enforced by enforceApiAccess — operations outside the whitelist → 405." },
39
+
"trackHistory": { "status": "dead", "evidence": "no behavior-changing reader (database-loader.trackHistory is a separate ctor option)" },
40
+
"searchable": { "status": "dead", "evidence": "no behavior-changing reader" },
41
+
"files": { "status": "dead", "evidence": "no behavior-changing reader" },
42
+
"feeds": { "status": "dead", "evidence": "no behavior-changing reader" },
43
+
"activities": { "status": "dead", "evidence": "no behavior-changing reader" },
44
+
"trash": { "status": "dead", "evidence": "no behavior-changing reader" },
45
+
"mru": { "status": "dead", "evidence": "no behavior-changing reader" },
46
+
"clone": { "status": "dead", "evidence": "no behavior-changing reader" }
47
+
}
48
+
},
36
49
"versioning": { "status": "dead", "evidence": "aspirational; no runtime" },
37
50
"partitioning": { "status": "dead", "evidence": "aspirational; no runtime" },
38
51
"cdc": { "status": "dead", "evidence": "aspirational; no runtime" },
0 commit comments