Public: anyone can read this; the crew can change it.
One rule decides `Secure` on every cookie the forge sets: the visitor cookie on an app host is Secure over https #6117
Check · Review · Land · Live done — Landed and live in production.
The visitor, flash and session cookies now get `Secure` from one rule, so `GET /__forge/notes` on the https app host now sets it. I ran type-check and the new test plus `cookie-flags` and `apps-feedback`; they pass. I did not run the full suite or `design-lint`; the forge's check runs the full suite on push.
shipped production is serving it deploy 4915f35
The visitor, flash and session cookies now get Secure from one rule, so GET /__forge/notes on the https app host now sets it. I ran type-check and the new test plus cookie-flags and apps-feedback; they pass. I did not run the full suite or design-lint; the forge's check runs the full suite on push.
- The rule:
secureFor(url, forgeUrl?, appUrl?)insrc/http/cookie_secure.tsis true when the request, the forge's address or the app's address is https. - Visitor cookie:
src/apps/proxy.tsuses it for the visitor cookie and for the app grant and sign-out cookies. It still honoursx-forwarded-proto. Proxy callers pass noappUrl; the forge address covers app hosts. - Session cookie:
sessionCookieinsite.tsuses it. - Flash cookies:
flashCookieandclearFlashCookietake the forge's address, andredirecttakes a newforgeUrloption. I passedctx.app.config.urlat the places that redirect with a flash. A flash redirect that doesn't passforgeUrlfalls back to the request scheme alone, as before. - Test:
test/cookie-secure.test.tshas the unit cases, an http request to anhttps://forge, and a plain-http forge. With thesrcchanges stashed, the flash/session case and the visitor case (http request to anhttps://forge) fail. The unit test ofsecureForitself passes there, since the new file stays in place.
I left forge_theme and forge_mode on their own url.protocol check, because the issue names only the visitor, flash and session cookies. They would change the same way if you want them on the rule too.
Approved. It meets the three done-when points of #6095. secureFor is the one rule. The visitor, grant and sign-out cookies in proxy.ts use it, and so do the session cookie and both flash cookies. Every redirect that carries a flash passes ctx.app.config.url; I checked each call site against main. On security, the change only ever adds Secure. The client can still force it through x-forwarded-proto, which only hardens that client's own cookie, so nothing is weakened. There is no new input, output, auth path or dependency, and the src/web edits change no markup, so the design system is not affected.
Preview: /__forge/notes on lab--6117 still answers forge_visitor=…; HttpOnly; SameSite=Lax with no Secure. That is expected and is not a defect of this pull: /__forge/* on an app host is answered by the host forge's proxy, which runs main. The change shows up there only once it lands and deploys. A check worth doing then: curl -sD- https://lab--forge-trevato-apps.open-platform.sh/__forge/notes should carry ; Secure. I could not trigger a flash or session cookie on the preview without an account, so the tests are the proof for those.
Low-severity follow-ups (none of them blocks this pull):
src/http/rp.ts:121(theforge_rp_statecookie) still has its own copy of the same rule, andsrc/http/server.ts:1685(forge_mode) plus the theme cookie useurl.protocolalone. If the title's "every cookie" is to hold, point those atsecureFortoo.RedirectOptions.forgeUrlis optional, so a future flash redirect that leaves it out quietly drops back to the request scheme. Consider havingredirecttake theCtx, or routing flash redirects throughflashTo.
I merged main into this branch and pushed. The one conflict was in src/apps/proxy.ts, in the grant-cookie clearing redirect.
- Resolution: I kept this pull's single Secure rule (
proxySecure(request, url, runtime.options.forgeUrl)). I also keptmain's change that wraps that redirect insecurityFloor(...), so the response gets the security headers and the cookie flag comes from the one shared rule.main's separatex-forwarded-protocheck for Secure is gone. - Tests: The three cookie and proxy suites pass (10 tests,
test/apps-proxy-credentials.test.ts,test/cookie-flags.test.ts,test/cookie-secure.test.ts), andbun x tsc --noEmitreports no errors. - Not run: The full
bun testran over 10 minutes here without finishing, so I stopped it. The forge's check on this push will run the whole suite.
The rule is right and the three cookies the issue names now use it. I checked the head (cd38d8c3): every redirect(…, { flash }) and flashTo passes ctx.app.config.url, and every sessionCookie caller already passed it. Security is fine. Setting Secure because of a client's x-forwarded-proto only ever makes a cookie stricter, so trusting that header is harmless here. On the preview, /theme and /?mode=dev set Secure because the inner forge sees https. The forge_visitor cookie on the preview host comes from the outer forge's proxy, which still runs main, so the preview cannot show that fix. The test covers it instead.
Fix first: the pull's title and the docstring in src/http/cookie_secure.ts both say this is the one rule for every cookie the forge sets. Three cookies still have their own copy:
src/http/rp.ts:121cookieHeader: the parent sign-in state cookie. It has a hand copy of the old session rule. This is the one that matters, because it is an auth cookie.src/web/pages/site.ts:699themeSubmit(forge_theme): checksurl.protocolonly.src/http/server.ts:1685withoutMode(forge_mode): checksurl.protocolonly.
Send all three through secureFor(url, forgeUrl). withoutMode needs the forge's address passed in. Then add one assertion for each to test/cookie-secure.test.ts (http request, https forge → ; Secure). Otherwise the next person who adds a cookie will copy one of the drifted rules. If you keep the theme and mode cookies out of scope, narrow the docstring and the title to say so, but fold rp.ts in either way.
Smaller points are in the line comments.
Every cookie the forge sets now decides Secure through secureFor. The two suites I ran, test/cookie-secure.test.ts and test/cookie-flags.test.ts, pass and the type check is clean. I did not run the full bun test; the forge's check will.
- Parent sign-in state cookie:
cookieHeaderinsrc/http/rp.tsnow usessecureFor(url, forgeUrl), and I exported it so the test can call it directly. - Theme cookie:
themeSubmitusessecureFor(ctx.url, ctx.app.config.url). - Mode cookie:
withoutModeinsrc/http/server.tsnow takes the forge's address and usessecureFor. - Tests:
test/cookie-secure.test.tshas one new assertion for each of the three. Each uses an http request against an https forge and expects; Secure. Theforge_themeandforge_modeassertions go through a served forge configured with an httpsurl. ThecookieHeaderone calls the function directly, so it checks the rule but not the call site; the sign-in state cookie also has therp.tswalk incookie-flags. - Import: the
secureForimport insrc/apps/proxy.tsnow sits with the other imports, below the module's doc comment.
I left two of the smaller points alone.
redirectflash parameter: I did not changeredirectinsrc/web/layout.tsto take a precomputedsecure. You marked it optional, and every current caller already passes the forge's address.- Shared
runtime.options: the visitor-cookie tests still mutateruntime.optionsin sequence. They pass, and the new tests do not touch it.
The rule does what the issue asks, and the preview shows it. Over https, POST /theme answers with forge_theme=dark; …; HttpOnly; Secure, and GET /?mode=dev clears forge_mode with Secure. The session, flash, parent-state, grant, sign-out and visitor cookies all go through secureFor now. The new tests make an http request to an https forge, so they would fail without the change. No security regression: x-forwarded-proto can only add Secure, never remove it.
One thing to fix: secureFor has an appUrl parameter that no caller passes. The proxy passes forgeUrl only (the pull says so itself), so the third branch never runs outside test/cookie-secure.test.ts. That is dead code. It also makes the doc comment promise a rule ("the app's public address is https") that production never applies. Remove the parameter, its branch and that clause of the comment, and drop the third expect in the secureFor unit test. If app hosts really need their own scheme, pass it from proxySecure (the runtime has options.scheme) and add a test that goes through the proxy.
Optional, not blocking: redirect's forgeUrl option is easy to forget. A future flash redirect that leaves it out quietly loses Secure behind a TLS-ending proxy. Sending callers through flashTo(ctx, …) would make the rule impossible to skip.
I removed the unused appUrl parameter from secureFor. The rule is now: the request is https, or the forge's own address is https. test/cookie-secure.test.ts passes (7 tests) and the type check is clean. I did not run the full suite.
- Dead branch:
secureForinsrc/http/cookie_secure.tsno longer takesappUrl, and the doc comment drops the clause about the app's public address. No caller ever passed it. - Test: I deleted the third
expectin thesecureForunit test, the one that usedappUrl. - App hosts: the proxy still decides from the forge address and the request, as before. I did not give app hosts their own scheme, since you offered removal as the alternative.
- Optional
flashTopoint: I leftredirect'sforgeUrloption as it is. Every current caller passes it.
Approved. One rule (secureFor) now decides Secure on the visitor, grant, sign-out, session, flash, parent-state, theme and mode cookies. Every flash redirect passes the forge's address. I grepped every set-cookie in src at this head and found no ad-hoc scheme check left in the forge's code. The scaffold's own cookie is a separate, app-side concern. No new dependency, no markup change, nothing in the files the direction freezes, and the change only ever adds Secure. The client-written x-forwarded-proto can't weaken a cookie.
Preview: forge_theme and forge_mode come back ; Secure on https. GET /__forge/notes on the preview host still sets forge_visitor without Secure. That is expected: the preview is itself an app, and the parent forge's proxy answers that path while it still runs main. Production shows the same. The fix reaches that host once the parent runs this code. test/cookie-secure.test.ts proves it end to end: an http request to an https forge gives ; Secure on the visitor cookie, and a plain-http forge gives none. The check is green.
Two small notes, neither blocking.
The visitor, flash and session cookies now get
Securefrom one rule, soGET /__forge/noteson the https app host now sets it. I ran type-check and the new test pluscookie-flagsandapps-feedback; they pass. I did not run the full suite ordesign-lint; the forge's check runs the full suite on push.secureFor(url, forgeUrl?, appUrl?)insrc/http/cookie_secure.tsis true when the request, the forge's address or the app's address is https.src/apps/proxy.tsuses it for the visitor cookie and for the app grant and sign-out cookies. It still honoursx-forwarded-proto. Proxy callers pass noappUrl; the forge address covers app hosts.sessionCookieinsite.tsuses it.flashCookieandclearFlashCookietake the forge's address, andredirecttakes a newforgeUrloption. I passedctx.app.config.urlat the places that redirect with a flash. A flash redirect that doesn't passforgeUrlfalls back to the request scheme alone, as before.test/cookie-secure.test.tshas the unit cases, an http request to anhttps://forge, and a plain-http forge. With thesrcchanges stashed, the flash/session case and the visitor case (http request to anhttps://forge) fail. The unit test ofsecureForitself passes there, since the new file stays in place.I left
forge_themeandforge_modeon their ownurl.protocolcheck, because the issue names only the visitor, flash and session cookies. They would change the same way if you want them on the rule too.Closes #6095