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
{{ message }}
Repository navigation
Generated forms and write routes expose credential fields; @formExclude does not block writes #484
Generated admin forms and generated write routes can expose credential fields.
Observed in two applications built on earlier releases (0.19.x to 1.1.0; not yet re-checked on 1.1.4):
Generated forms rendered secret hashes (for example a session tokenHash and a credential pinHash) as editable inputs.
Marking a field @formExclude kept it out of the generated form, but the generated create/update routes still accepted writes to that field. Each application had to add allowlists so new entities emitted no routes, plus a hand-written server guard.
Expected:
A field that is excluded from forms, or that is marked as a credential/secret, is also refused by the generated write routes (create, update, patch) and is never returned by generated read routes unless explicitly allowed.
The same rule in every port that generates forms or routes, pinned by the API-contract conformance corpus.
First step: reproduce on 1.1.4 in each port and record which ports are affected.
Which ports were affected: all five. On main the new fixtures/api-contract-conformance/secret-fields/ corpus (generated lane) failed in TypeScript, Java, Kotlin, Python and C#, both scenarios in each: a GET returned pinHash, and a PATCH wrote the forged value into tokenHash and pinHash. After the fix both scenarios pass in all five ports, and the write path is also asserted by reading the stored row back.
Existing vocabulary only, no new attributes. A credential is a field that declares or inherits a child view.password. A field is write-denied when its effective @formExclude is true or it is a credential. Generated create, PATCH and PUT ignore write-denied fields (stripped, never a 400, like @mutability: writeOnce). A credential key is omitted from list, get, write echoes, M:N targets and TPH rows. A @formExclude-only field is still returned. Filter and sort allowlists are unchanged.
Judgment calls to review:
view.password is the credential marker because it is the only credential vocabulary that exists. There is no override to let a generated route write a denied field; hand-write the route for that.
Behaviour change for upgraders: a @required field that is also write-denied makes the generated create fail, because the value is dropped. This is in the CHANGELOG entry and docs/features/api-contract.md.
Served reports are redacted in every port: a report that groups by a credential, or takes its min or max, omits the column from the list route and its export, and the column is neither filterable nor sortable.
Some TPH shared-column cases, where one subtype marks a column and another does not, are pinned by codegen text checks, not an HTTP scenario.
The same PR also carries the five docs recipes and a clear error for dialect: "d1" (use "sqlite"). The Workers/D1, drizzle-kit and auth-scope recipes are typechecked, not run.
Activity
dmealing commentedon Oct 28, 2026
Fix is up in #503.
Which ports were affected: all five. On
mainthe newfixtures/api-contract-conformance/secret-fields/corpus (generated lane) failed in TypeScript, Java, Kotlin, Python and C#, both scenarios in each: a GET returnedpinHash, and a PATCH wrote the forged value intotokenHashandpinHash. After the fix both scenarios pass in all five ports, and the write path is also asserted by reading the stored row back.Existing vocabulary only, no new attributes. A credential is a field that declares or inherits a child
view.password. A field is write-denied when its effective@formExcludeis true or it is a credential. Generated create, PATCH and PUT ignore write-denied fields (stripped, never a 400, like@mutability: writeOnce). A credential key is omitted from list, get, write echoes, M:N targets and TPH rows. A@formExclude-only field is still returned. Filter and sort allowlists are unchanged.Judgment calls to review:
view.passwordis the credential marker because it is the only credential vocabulary that exists. There is no override to let a generated route write a denied field; hand-write the route for that.@requiredfield that is also write-denied makes the generated create fail, because the value is dropped. This is in the CHANGELOG entry anddocs/features/api-contract.md.minormax, omits the column from the list route and its export, and the column is neither filterable nor sortable.The same PR also carries the five docs recipes and a clear error for
dialect: "d1"(use"sqlite"). The Workers/D1, drizzle-kit and auth-scope recipes are typechecked, not run.fix(codegen): a served report hides a credential column in every port (…
fix(codegen): a credential report column is neither filterable nor so…
fix: close adopter defects for 1.2.0 across Python routers, filter qu…