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
Commit bac5b4a
Browse filesBrowse the repository at this point in the historyBrowse files
fix(security): relocate the internal-field write-response strip to the generic-data-path ingress, and route better-auth session readbacks through resolveInternalField (#7823)
A-prime + compose, per the 2026-08-13 maintainer ruling:
- objectql: the engine's two omitInternalFields write-response sites are
removed — engine write results stay whole, so better-auth's createWithHooks
reads the minted sys_session.token back and signIn/signUp work. The generic
READ-path strip is unchanged.
- metadata-protocol: omitInternalFieldsFromWriteResponse (single exported
helper) applied by every *Data write face — createData / cloneData /
updateData / createManyData / insertManyData / updateManyData / batchData —
plus a tripwire test that enumerates the *Data surface, fails on any face a
flagged sentinel reaches, and fails on any new face with no recipe.
- rest: the cross-object batch update mouth (direct ql.update) applies the
same strip through the protocol instance (dormant today — no flagged object
grants bulk — wired so the guarantee does not depend on that).
- plugin-auth: session rows read back through the better-auth adapter get
token re-attached via Engine.resolveInternalField (#8118's accessor), so
revoke-other-sessions / sliding refresh / expired cleanup act again while
the generic data API keeps returning token-less rows. Fail-closed when a
stripped row meets an engine without the accessor.
- dogfood: revoke-other-sessions pinned on the other session's liveness, and
expired-session cleanup pinned on the row, not the status code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy
Copy file name to clipboardExpand all lines: .changeset/session-token-internal.md
+44-11Lines changed: 44 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,13 +1,20 @@
1
1
---
2
2
"@objectstack/platform-objects": patch
3
+
"@objectstack/objectql": patch
4
+
"@objectstack/metadata-protocol": patch
5
+
"@objectstack/plugin-auth": patch
6
+
"@objectstack/rest": patch
3
7
---
4
8
5
-
fix(platform-objects): `sys_session.token` stops serializing on the data API — `internal: true` (#7823)
9
+
fix(security): `sys_session.token` stops serializing on the data API — `internal: true`, with the write-response strip relocated to the generic-data-path ingress (#7823)
6
10
7
11
<!-- adr-0087: not-required (no-migration-prescription) One field-level flag added
8
-
to one existing declaration. Nothing is renamed, retired or tombstoned, so there
9
-
is no conversion to register. The only behavioural change is that a field which
10
-
already DECLARED it was never exposed stops being exposed. -->
12
+
to one existing declaration, plus an internal relocation of where that flag's
13
+
write-response half is enforced (engine write sites → the metadata-protocol
14
+
ingress). Nothing authorable is renamed, retired or tombstoned, so there is no
15
+
conversion to register. The behavioural changes are that a field which already
16
+
DECLARED it was never exposed stops being exposed, and that better-auth's
17
+
session-lifecycle routes keep working while it does. -->
11
18
12
19
`sys_session.token` — the **live bearer credential** for an active session —
13
20
declared `description: 'Opaque session token — never exposed in UI'` and then
@@ -35,10 +42,34 @@ off the data API, authenticates as that member when sent as
35
42
**impersonation**, and any admin-adjacent read (an integration, a leaked admin
36
43
API response, a support tool) inherited it.
37
44
38
-
**The fix is one declaration.**`internal: true` — the opt-in, type-independent
39
-
flag minted by #7728 meaning *the declared value is never returned on the generic
40
-
data path* — is honoured at `Engine.maskSecretFields`' collector branch and at the
41
-
two write-response sites. No spec or engine change was needed.
45
+
**The fix is one declaration plus one relocation** (maintainer ruling
46
+
2026-08-13, "A-prime + compose"):
47
+
48
+
-`sys_session.token` is declared `internal: true` — the opt-in,
49
+
type-independent flag minted by #7728 meaning *the declared value is never
50
+
returned on the generic data path*. The engine's READ-path strip is
51
+
unchanged and closes the disclosure.
52
+
- The flag's **write-response** half moves out of the engine's insert/update
53
+
result paths — where it conflated "never on the generic data path" with
54
+
"never returned to the engine-level writer" and broke `signIn`/`signUp`
55
+
(better-auth reads the minted session row back off the insert result) —
56
+
into the **generic-data-path ingress**: every `*Data` write face in
57
+
`@objectstack/metadata-protocol` routes its response records through the
58
+
single exported helper `omitInternalFieldsFromWriteResponse`, held there by
59
+
a tripwire test that enumerates the ingress surface and fails on any face
60
+
the sentinel reaches (or any new `*Data` face with no recipe). The
61
+
`sys_api_key.key` PATCH-body closure (#7728's fourth surface) is preserved
62
+
at the ingress, byte-for-byte for callers. `@objectstack/rest`'s
63
+
cross-object batch update — the one write mouth outside the protocol —
0 commit comments