Repository navigation
Persist the tester cookie for 30 days so it survives browser restarts - #1137
Conversation
The set-tester endpoint minted the ts-tester cookie without Max-Age or Expires, making it a session cookie. Safari deletes session cookies when the browser quits, so Safari testers silently fell back to the baseline arm on every restart. Add a 30-day Max-Age; /_ts/clear-tester already expires the cookie explicitly and is unchanged.
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Correct one-line fix for a real Safari defect: the tester cookie was session-scoped, so Safari testers silently fell back to the baseline arm after every browser restart. The Max-Age addition is right, the named const carries its rationale, and /_ts/clear-tester was correctly left alone (already Max-Age=0). One blocking gap: the two published docs pages that quote the exact Set-Cookie line were not updated, so the operator-facing docs now contradict the code.
1 of the inline comments below carries a one-click GitHub
suggestion— use Commit suggestion to apply it as a commit on the PR branch. The other comments describe the change in prose because the files are outside this diff or the wording is yours to choose.
Blocking
🔧 wrench
- Published docs still print the pre-
Max-AgeSet-Cookieline — see the Cross-cutting section below (both files are outside this diff, so there is no inline anchor)
Non-blocking
🤔 thinking
- Safari caps this at 7 days behind a CNAME — see inline at
crates/trusted-server-core/src/tester_cookie.rs:22 - 30-day self-service enrollment window — see the Cross-cutting section below
⛏ nitpick
- Public doc links a private const (new rustdoc warning) — see inline at
crates/trusted-server-core/src/tester_cookie.rs:45
🌱 seedling
- Keep
ts-testeroff any future ignorable-cookie cache allowlist — see the Cross-cutting section below
👍 praise
- Test-first, with the why captured in the const — see the Cross-cutting section below
Cross-cutting / body-level findings
-
🔧 Published docs still print the pre-
Max-AgeSet-Cookieline — Two operator-facing docs quote the emitted header verbatim and were not updated, so both are now factually wrong and both ship to the docs site viadeploy-docs.yml:docs/guide/api-reference.md:36—Set-Cookie: ts-tester=true; Domain=<publisher.cookie_domain>; Path=/; Secure; SameSite=Laxdocs/guide/configuration.md:467— the same string
The repo's own precedent is that these pages move with the header:
docs/guide/api-reference.mdwas updated in the very commits that introduced these routes (563ec96d, #795 and3dc1262c, #797).Proposed fix — append the attribute to both
httpblocks:Set-Cookie: ts-tester=true; Domain=<publisher.cookie_domain>; Path=/; Secure; SameSite=Lax; Max-Age=2592000 Cache-Control: no-store, private
Worth one sentence alongside each block noting the 30-day lifetime and that
/_ts/clear-testeris the opt-out, sinceconfiguration.mdalready documents the clear path'sMax-Age=0. Runnpm run formatindocs/afterwards to satisfy the requiredformat-docsgate. Apply manually — can't be auto-applied as a suggestion because neither file is part of this diff. -
🤔 30-day self-service enrollment window —
/_ts/set-testeris unauthenticated whenever[tester_cookie].enabled = true, and the cookie is scoped to the wholepublisher.cookie_domain. Before this change a stray visit self-limited at browser close; now it pins that browser to the tester arm for a month, and/_ts/clear-testeris the only opt-out — which a non-QA visitor has no reason to know about. Shared and kiosk browsers now carry the arm for 30 days.That is very likely fine: the module doc already states the cookie only affects tester routing and must not gate sensitive behavior, and the feature is off by default. Worth confirming explicitly that a 30-day contamination window is acceptable for whatever consumes the arm (measurement purity in particular), and that the flag is expected to be enabled only during QA windows rather than left on.
-
🌱 Keep
ts-testeroff any future ignorable-cookie cache allowlist — No action for this PR; flagging for whoever lands a per-cookie template-cache policy.Today persistence is harmless for the shared template cache:
TemplateCacheBypassReason::CookieForwarded(crates/trusted-server-core/src/publisher.rs:5706) disqualifies any cookie-bearing request, andtemplate_cache_varyrejectsCookieoutright (crates/trusted-server-core/src/creative_opportunities.rs:478).But TS forwards
Cookieto origin unchanged — there is noCookiestrip on the publisher path — so if a future policy treats some cookies as ignorable for cache-key purposes,ts-testermust not be one of them. An origin that varies the document on the tester arm would otherwise have tester HTML stored under a reader-neutral key and served to every reader. This change widens the window in which a tester's browser carries the cookie from one session to a month, which makes that mistake more likely to be observed rather than less. -
👍 Test-first, with the why captured in the const — Both assertions were updated and watched failing before the implementation landed; the const carries the Safari session-cookie rationale rather than a bare
2592000; the assertion messages were tightened to state the new contract instead of being left describing only the domain; andformat_clear_tester_cookiewas correctly left untouched. Verified independently:Set-Cookiealso survives EC finalize becausecrates/trusted-server-core/src/ec/cookies.rs:144,167useappendrather thaninsert, and nothing rewritesSet-Cookieattributes on egress (response_privacyonly downgradesCache-Control).
CI Status
- cargo fmt: PASS (required)
- cargo test: PASS (required)
- format-docs: PASS (required)
- format-typescript: PASS (required)
- cargo test (axum native): PASS
- cargo test (cloudflare native): PASS — reported as
cargo check (cloudflare native + wasm32-unknown-unknown) - cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test (cross-adapter parity): PASS
- cargo test (ts CLI, native): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
- vitest: PASS
- CodeQL: PASS
- Analyze (rust): PASS
- Analyze (javascript-typescript): PASS
- Analyze (actions): PASS
aram356
left a comment
There was a problem hiding this comment.
Summary
The fix itself is correct and matches #1136: Max-Age=2592000 is appended in format_tester_cookie, the clear path keeps its explicit Max-Age=0, and both exact-string test assertions were updated. Requesting changes for two things the diff leaves open: the lifetime should be operator-configurable rather than a hard-coded constant, and the guide docs still show the old Set-Cookie header verbatim.
None of the inline comments carry a one-click
suggestion— both fixes span files outside this diff, so they are described in prose and have to be applied manually.
Blocking
🔧 wrench
- Make the tester-cookie lifetime configurable — see inline at
crates/trusted-server-core/src/tester_cookie.rs:22
Non-blocking
🤔 thinking
- Safari may cap the lifetime to 7 days — see inline at
crates/trusted-server-core/src/tester_cookie.rs:19
Cross-cutting / body-level findings
- 🔧 Docs still show the pre-change
Set-Cookieheader —docs/guide/configuration.md:536anddocs/guide/api-reference.md:36both document the exact set-tester response header (ts-tester=true; Domain=<publisher.cookie_domain>; Path=/; Secure; SameSite=Lax) withoutMax-Age, so the guide now contradicts the server and will mislead QA reading the wire format. Please update both code blocks to includeMax-Age, and once the lifetime is configurable (see the inline comment atcrates/trusted-server-core/src/tester_cookie.rs:22) document themax_age_secondskey and its 30-day default in the[tester_cookie]field table atdocs/guide/configuration.md:529.
CI Status
- cargo test: PASS (required)
- cargo fmt: PASS (required)
- format-docs: PASS (required)
- format-typescript: PASS (required)
- cargo test (ts CLI, native): PASS
- vitest: PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test (cross-adapter parity): PASS
- Analyze (javascript-typescript): PASS
- Analyze (actions): PASS
- cargo test (axum native): PENDING
- Analyze (rust): PENDING
- prepare integration artifacts: PENDING
- CodeQL: SKIPPED
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Reviewed 3c1b2dd3a57d6d69d076036eec2b9d23f1796ce9 against d704d0ab0c5916d429b80a5707c0f2d74b99cab1. No additional actionable findings beyond the issues already recorded in unresolved review feedback.
The Fastly route and core tests confirm that /_ts/set-tester emits Max-Age=2592000, remains non-cacheable, and that /_ts/clear-tester retains the matching scope with Max-Age=0. Focused WASM tests and cargo fmt --all -- --check passed, and all reported CI checks are green.
…age_seconds Replace the hard-coded 30-day constant with a validated config field (1 second to 1 year) that defaults to 30 days. A manual Default impl keeps the absent-section path on the same default, since Settings.tester_cookie uses serde(default). Document the new key in the guide, the API reference, and the example config.
|
@aram356 to open new PR |
Brings #1175 to 3add18d, which carries main through 5319594 and repins EdgeZero to v0.0.9. Conflict resolutions: - Fastly and Axum entry points keep the RC's request-timing stack and adopt main's reusable sandbox (#1179) and RUST_LOG handling (#1227). Sandbox counters are threaded through send_edgezero_response alongside SendContext, following the shape used in #1261. - tester_cookie.rs takes main's final #1137. - #1175's own docs, CLI test and Viceroy fixture take its new revision. - browser.rs drops the RC-only launch timeout const that main's #1256 now defines.
Fixes #1136
Problem
GET /_ts/set-testerminted thets-testercookie withoutMax-AgeorExpires, making it a session cookie. Safari deletes session cookies when the application quits, so Safari testers lost the cookie on every restart and silently fell back to the baseline arm on their next visit. Chrome and Firefox mask the defect because session restore resurrects session cookies.Change
TESTER_COOKIE_MAX_AGE_SECONDS(30 days, 2592000) and appendMax-Ageto the tester cookie informat_tester_cookieSet-Cookiestring (coretester_cookietest, fastlydispatch_set_testertest)/_ts/clear-testeralready expires the cookie explicitly withMax-Age=0and is unchanged. The module documents that the cookie only affects tester routing and must not gate sensitive behavior, so the bounded persistent lifetime does not change its security posture.Testing
cargo test-fastly: 170 adapter + 2357 core tests passcargo test-axum: passcargo fmt --all -- --check,cargo clippy-fastly,cargo clippy-axum: cleanGenerated with Claude Code