Repository navigation
feat(web): sign in from Tailscale Serve identity headers (phase 1) - #457
Conversation
Adds ENGRAM_REMOTE_WEB_AUTH=tailscale-serve: the receiver mints the existing
hardened Web session from the Tailscale-User-Login header that the local
tailscale serve proxy asserts, matched against exact-login viewer/editor
allowlists (ENGRAM_REMOTE_WEB_VIEWERS / ENGRAM_REMOTE_WEB_EDITORS).
Credential mode stays the default and is byte-for-byte unchanged. Identity
mode forbids shared Web credentials, requires a loopback bind, accepts only
a `{}` login body with an exact Origin, and never reads the header in
credential mode. The page ships the credential form hidden, signs in
silently on a 401 probe, shows the login, and re-mints once after an
expired read with a five-second floor.
Design: docs/superpowers/specs/2026-10-08-web-tailscale-identity-auth-design.md
Ledger: "Web Reader and Editor Authority" rewritten for both modes.
Also records the 2026-10-08 HQ status check and the Serve header pre-check.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: febdf9a615
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let web, web.usesTailscaleServeIdentity, !Self.isLoopbackBindAddress(host) { | ||
| throw EngramRemoteWebConfig.ConfigError.identityRequiresLoopbackBind |
There was a problem hiding this comment.
Revalidate the loopback bind when constructing the app
This guard protects only configurations produced by fromEnvironment; EngramRemoteServerConfig also has a public initializer and publicly mutable host/web properties, while EngramRemoteServerApp later binds config.host without repeating this check. An embedding caller can therefore construct an identity-mode config with host: "0.0.0.0" (or mutate a validated config before app initialization), exposing the endpoint to peers that can forge Tailscale-User-Login and mint editor sessions. Reapply the identity-mode loopback validation in EngramRemoteServerApp.init, where the final configuration is consumed.
Useful? React with 👍 / 👎.
Summary
Implements phase 1 of
docs/superpowers/specs/2026-10-08-web-tailscale-identity-auth-design.md: the Web reader can sign in from the identity that the localtailscale serveproxy asserts instead of a shared credential.EngramRemoteWebConfig: newAuthMode(credential(viewerDigest:editorDigest:)ortailscaleServe(viewers:editors:)),ENGRAM_REMOTE_WEB_AUTH(credentialdefault,tailscale-serveopt-in),ENGRAM_REMOTE_WEB_VIEWERS/ENGRAM_REMOTE_WEB_EDITORSexact-login allowlists, five newConfigErrorcases. Identity mode rejects any*_CREDENTIALkey.EngramRemoteServerConfig: identity mode requires a loopbackENGRAM_REMOTE_HOST(the Serve proxy is the only legitimate source of the header).WebAuthSessionStore:WebIdentity,login(identity:)(no attempt window; same capacity and collision guards),LoginResult.forbidden, per-session login,actor(sessionToken:). Each store answers.unavailableto the other mode's entry point.WebRequestBoundary.tailscaleIdentity(in:): exactly one well-formedTailscale-User-Login, read only in identity mode.WebAuthRoutes: identity-modePOST /web/api/authaccepts only{}with the existing exact-Origin check (401 no header, 403 unlisted, 204 cookie); identity-modeGET /web/api/authaddslogin. Credential-mode responses are unchanged.WebUIRoutes.mount(tailscaleServeIdentity:): same HTML with<body data-auth-mode="tailscale-serve">and the credential form shippedhidden; the script signs in silently on a 401 probe, showsSigned in as <login>, hides form and logout, re-mints once after an expired read with a five-second floor, and shows the not-permitted copy on 403.cutover-web-editor-hardening-1annotated.CHANGELOG.md/MEMO.md: the 2026-10-08 HQ status check, the old local Service CPU finding, and the owner-authorized Serve header pre-check (PASS on HQ, Tailscale 1.102.5).Decisions made during implementation
401body, so credential-mode responses stay byte-identical and no request is spent on mode discovery.document.body && document.body.dataset) becausetests/scripts/collector-web-ui.test.tsruns the shipped script in a fake DOM withoutbody; the first draft failed 173 of its tests for that reason.Phase 2 (audit actor through the Web write envelope, replacing
mcp) is a separate PR.Tests
WebConfigTests:testAuthModeDefaultsToCredentialAndRejectsUnknownValues,testTailscaleServeModeParsesExactAllowlistsAndKeepsNoCredentialDigest,testTailscaleServeModeForbidsSharedCredentialsAndRequiresViewers,testIdentityAllowlistEntriesMustBeExactPrintableLoginsWithoutDuplicates.WebAuthSessionTests:testIdentityLoginMintsViewerOrEditorByExactLoginAndRecordsActor,testIdentityLoginsIgnoreTheAttemptWindowButRespectCapacityAndExpiry,testEachStoreAnswersOnlyItsOwnModeAndCredentialSessionsHaveNoActor.WebAuthRouteTests:testIdentityLoginMintsFromTheServeHeaderWithEmptyBodyAndExactOrigin,testIdentityLoginRejectsCredentialBodiesMissingDuplicateAndUnlistedLogins,testCredentialModeIgnoresForgedServeHeadersAndKeepsItsStatusBody,testIdentityViewerSessionCannotReachWriteRoutesEvenWithAnEditorHeader.WebUIRoutesTests:testIdentityModeMarksTheDocumentAndTheScriptSignsInSilently.WebServerIntegrationTests:testEnvironmentIdentityModeRequiresLoopbackBindAndNoCredential.tests/scripts/collector-web-ui.test.ts: harnessauthModeoption and four behavioral tests (silent sign-in, 403 copy with the form hidden, one re-mint with the five-second floor, credential mode never posts an identity login).Validation
CHECKS_RUN:
EngramRemoteServerCorescheme on the final source (frommacos/,xcodebuild ... build-for-testingthentest-without-building,CODE_SIGNING_ALLOWED=NO):** TEST EXECUTE SUCCEEDED **, 519 tests, 0 failures.npx vitest run tests/scripts/collector-web-ui.test.ts: 180 passed;tests/scripts/invariants-ledger.test.ts: 12 passed.npm run lintexit 0;bash scripts/check-swift-conventions.shandbash scripts/check-swift-module-boundaries.shexit 0.tailscale serve --bg --https=8444, requested from HQ and from the Daily Mac with spoofedTailscale-User-Login; both echoes carried exactly one Serve-asserted login and the spoofed values were removed; mapping removed afterwards.CHECKS_NOT_RUN:
web-env-filecheck for identity-mode environments (design doc Q4): verify at deploy.