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
sys_organization's platform-owned columns are engine-writable but no product surface reaches them — the data door answers 405 (apiMethods: ['get', 'list']) and better-auth's update endpoint does not carry them #15873
Found while landing #14238's sys_organization.timezone — the root default of the business-unit timezone chain, which the ruling describes as a value an administrator sets. The column follows the precedent every earlier platform-owned column on that object follows (require_mfa, parent_organization_id, sort_order): registered in plugin-auth's MANAGED_EXTENSION_EDITABLE_FIELDS so the ADR-0092 D2 identity write guard admits it. This card is about where that precedent leads. Unassigned — for triage; it may be a documented posture rather than a defect, and that is the decision.
packages/rest/src/rest-server.ts:1699 — a non-empty enable.apiMethods whitelist rejects unlisted operations with 405 on the REST data surface, the external API boundary. So PATCH /api/v1/data/sys_organization/ID never reaches the engine.
The object's update_organization row action targets better-auth's organization/update (bodyShape: { wrap: 'data' }) with params name, slug, logo only; the extension fields are deliberately not better-auth additionalFields (the ai_access / locale notes in auth-manager.ts), so that endpoint does not carry them either.
Writers of require_mfa outside plugin-auth's own read site (auth-manager.ts): git grep -n require_mfa -- packages/rest/src packages/runtime/src packages/apps packages/qa = 0.
So the four columns are settable only by a system-context caller — a plugin, a flow, a CLI seed, SQL. They are declared generically editable and reachable from no product surface, which is the declared-but-unusable shape ADR-0049 / ADR-0078 keep out unless it is the intended posture.
Not measured
Whether the Setup app (objectui side) renders an organization edit form for these columns at all; if it does, it meets the 405 above. Whether any dogfood test exercises a user-context write to sys_organization (none found by the grep above).
Fix space (a decision, not a prescription)
(a) admit update in sys_organization.apiMethods and let the D2 whitelist do the column gating — the whitelist already exists for exactly this, and better-auth's own columns stay stripped;
(b) a dedicated admin route for the platform-owned columns;
(c) declare the posture: the columns are system-writable only, and drop them from MANAGED_EXTENSION_EDITABLE_FIELDS so the declaration matches the reach.
The #14238 seat did not touch apiMethods: the method gate on an identity table is an ADR-0092 / #1591 decision, outside ruling A's scope.
Published surface (measured)
sys_organization's apiMethods is in the built packages/platform-objects/dist/identity/index.js / index.mjs; content/docs/** was not measured for this observation.
Found while landing #14238's
sys_organization.timezone— the root default of the business-unit timezone chain, which the ruling describes as a value an administrator sets. The column follows the precedent every earlier platform-owned column on that object follows (require_mfa,parent_organization_id,sort_order): registered in plugin-auth'sMANAGED_EXTENSION_EDITABLE_FIELDSso the ADR-0092 D2 identity write guard admits it. This card is about where that precedent leads. Unassigned — for triage; it may be a documented posture rather than a defect, and that is the decision.The reading
Measured on
origin/mainat7b6825477:packages/platform-objects/src/identity/sys-organization.object.ts—enable.apiMethods: ['get', 'list'](its own comment: managedBy is not enforced: generic CRUD bypasses better-auth on sys_team (data-integrity / security) #1591, reads only, writes refused by the identity write guard and owned by better-auth; "HTTP answers 405 before the 403").packages/rest/src/rest-server.ts:1699— a non-emptyenable.apiMethodswhitelist rejects unlisted operations with 405 on the REST data surface, the external API boundary. SoPATCH /api/v1/data/sys_organization/IDnever reaches the engine.packages/plugins/plugin-auth/src/managed-extension-fields.ts—MANAGED_EXTENSION_EDITABLE_FIELDS.sys_organization=require_mfa,parent_organization_id,sort_order(andtimezoneonce No platform object carries a timezone, so every app that computes a date boundary has to invent one — and each will invent it differently #14238's PR lands), registered atkernel:readyas the guard's per-object update whitelist (auth-plugin.ts, the D7 loop). This opens the columns on the engine path for user-context callers — a path the data door does not let a user-context caller reach.update_organizationrow action targets better-auth'sorganization/update(bodyShape: { wrap: 'data' }) with paramsname,slug,logoonly; the extension fields are deliberately not better-authadditionalFields(theai_access/localenotes inauth-manager.ts), so that endpoint does not carry them either.require_mfaoutside plugin-auth's own read site (auth-manager.ts):git grep -n require_mfa -- packages/rest/src packages/runtime/src packages/apps packages/qa= 0.So the four columns are settable only by a system-context caller — a plugin, a flow, a CLI seed, SQL. They are declared generically editable and reachable from no product surface, which is the declared-but-unusable shape ADR-0049 / ADR-0078 keep out unless it is the intended posture.
Not measured
Whether the Setup app (objectui side) renders an organization edit form for these columns at all; if it does, it meets the 405 above. Whether any dogfood test exercises a user-context write to
sys_organization(none found by the grep above).Fix space (a decision, not a prescription)
updateinsys_organization.apiMethodsand let the D2 whitelist do the column gating — the whitelist already exists for exactly this, and better-auth's own columns stay stripped;MANAGED_EXTENSION_EDITABLE_FIELDSso the declaration matches the reach.The #14238 seat did not touch
apiMethods: the method gate on an identity table is an ADR-0092 / #1591 decision, outside ruling A's scope.Published surface (measured)
sys_organization'sapiMethodsis in the builtpackages/platform-objects/dist/identity/index.js/index.mjs;content/docs/**was not measured for this observation.