fix(session): inject __enketo_meta_* metadata server-side so cookies can be HttpOnly - #4
Draft
FrankApiyo wants to merge 3 commits into
Draft
fix(session): inject __enketo_meta_* metadata server-side so cookies can be HttpOnly#4FrankApiyo wants to merge 3 commits into
FrankApiyo wants to merge 3 commits into
Conversation
…can be HttpOnly Read __enketo_meta_* values from signed cookies on the server and inject them into the webform page (window.__enketoSession) instead of relying on enketo-core reading document.cookie client-side. This removes the requirement that these cookies be JS-readable, so they can be set HttpOnly by enketo-express and by upstream data servers (onadata/zebra/zonkey). - survey-controller: build session from req.signedCookies (only present props, so missing values fall through to the existing readCookie fallback); set __enketo_meta_deviceid HttpOnly - authentication-controller: set __enketo_meta_username HttpOnly - webform.pug: serialize session to window.__enketoSession with </script>-breakout escaping - controller-webform: thread session into all Form (re)inits enketo-core is unchanged: createSession already prefers the injected sessObj over readCookie.
The offline (/x/) page is cached by the service worker, so a session injected at render time can carry stale identity, and its client-side cookie read needs JS-readable cookies. Meta cookies are therefore HttpOnly only when offline is disabled, and session injection skips the offline view. Also extends the server specs: tampered-signature rejection, </script> escaping, offline no-bake, HttpOnly in both offline modes, setToken coverage via the CSRF login flow, chai assertions, and offline-enabled state restored after the suite.
FrankApiyo
marked this pull request as draft
August 4, 2026 07:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🗒️ Checklist
<type>(<scope>)<!>: <title> DEV-1234📣 Summary
Form-session metadata (username, device id, etc.) is now read on the server, so the cookies that carry it no longer need to be readable by page JavaScript — hardening them against script-injection attacks.
👷 Description for integrators
__enketo_meta_*session cookies had to be readable by client-side JavaScript — enketo-core reads them fromdocument.cookieto populate/session/contextsubmission metadata — which forced every issuer to set them withoutHttpOnly, exposing them to XSS exfiltration. The server now reads them fromreq.signedCookiesand injects them into the page; enketo-core's existing fallback chain is unchanged, so any value the server doesn't supply still falls back to the client-side read.Bonus:
req.signedCookiesverifies the cookie signature; the old client-side read stripped it and trusted the value unverified.💭 Notes
offline enabledis true, meta cookies stay JS-readable and the service-worker-cached/x/page keeps the legacy client-side cookie read — a session injected at render time would go stale in the cached page (wrong user after a user switch). Issuing services should mark their cookiesHttpOnlyonly for deployments that do not serve offline forms.__enketo_meta_*cookies with enketo's sharedencryption key(they already do). Once this is deployed they can mark these cookiesHttpOnly(subject to the offline caveat above), and any interim workarounds that kept them JavaScript-readable can be reverted.HttpOnlyprotects here is the signed cookie itself (no exfiltration of a replayables:value.sigcredential); the metadata values remain readable by page JavaScript viawindow.__enketoSessionsince the form engine needs them.</script>breakout escaping, both offline modes for the cookie flags, and the login flow's username cookie. CI runs the full server suite.👀 Preview steps
__enketo_meta_usernamecookie markedHttpOnly/session/contextmetadata hasusernameasprop not found— the client-side cookie read can't see anHttpOnlycookie