From 4101138f15aa912212510ffb2bf43e3bd71937cb Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Fri, 15 May 2026 21:49:09 +0200 Subject: [PATCH] docs(08): verify social sign-in threat mitigations --- .../phases/08-social-sign-in/08-SECURITY.md | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .planning/phases/08-social-sign-in/08-SECURITY.md diff --git a/.planning/phases/08-social-sign-in/08-SECURITY.md b/.planning/phases/08-social-sign-in/08-SECURITY.md new file mode 100644 index 0000000..ef2c26e --- /dev/null +++ b/.planning/phases/08-social-sign-in/08-SECURITY.md @@ -0,0 +1,84 @@ +--- +phase: 8 +slug: social-sign-in +status: verified +threats_open: 0 +asvs_level: 1 +created: 2026-05-15 +updated: 2026-05-15 +register_authored_at_plan_time: true +--- + +# Phase 8 — Security + +> Threat verification for Phase 8 social sign-in after UAT scope change: Google sign-in remains active; Apple sign-in is disabled and hidden. + +--- + +## Trust Boundaries + +| Boundary | Description | Data Crossing | +|----------|-------------|---------------| +| Browser to Xtablo auth routes | User starts Google sign-in and returns through callback routes | OAuth state, nonce, authorization code, session cookies | +| Xtablo to Google OAuth/OIDC | Server exchanges code and verifies ID token | Authorization code, client credentials, ID token claims | +| Xtablo auth to Postgres | Server creates or links local users and provider identities | User email, provider subject, provider metadata, session rows | +| Auth UI to email/password forms | Social provider controls coexist with existing CSRF-protected forms | CSRF token, form fields, provider start links | +| Authenticated account page to identity data | User views linked provider status | Current user's provider identity rows only | + +--- + +## Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation | Status | +|-----------|----------|-----------|-------------|------------|--------| +| T-08-01 | Tampering / spoofing | `user_identities` linking | mitigate | `user_identities` has unique `(provider, provider_subject)` and `linkProviderUser` looks up provider subject inside a transaction before email matching. Evidence: `backend/migrations/0006_social_identities.sql`, `backend/internal/web/handlers_social.go`. | closed | +| T-08-02 | Spoofing | social-only signup conflict | mitigate | Signup blocks duplicate email/password signup for NULL-password social-only users with provider guidance. Evidence: `IsSocialOnlyUserByEmail`, `SignupPostHandler`, `TestSignup_SocialOnlyExistingUserShowsProviderMessage`. | closed | +| T-08-03 | Elevation of privilege | provider profile storage | mitigate | Display name and avatar are stored only as metadata in `user_identities`; authorization and session identity use local `users.id`. Evidence: `InsertUserIdentity`, `linkProviderUser`. | closed | +| T-08-04 | CSRF / spoofing | Google callback | mitigate | Callback validates OAuth state cookie before token exchange or linking. Evidence: `GoogleCallbackHandler`, `TestGoogleCallbackInvalidStateRejectedBeforeExchange`. | closed | +| T-08-05 | Spoofing / tampering | Google ID token | mitigate | OIDC verifier validates issuer, audience, expiry, and subject; handler requires verified email and nonce. Evidence: `auth.OIDCVerifier.Verify`, `GoogleCallbackHandler`, `TestGoogleCallbackUnverifiedEmailRejected`. | closed | +| T-08-06 | Elevation of privilege | Account linking | mitigate | Existing provider subject wins first; new link by email only occurs after verified provider email and inside one transaction. Evidence: `linkProviderUser`, `TestGoogleCallbackExistingSubjectWinsWhenEmailChanges`, `TestGoogleCallbackEmailUpdateConflictDoesNotRelinkSubject`. | closed | +| T-08-07 | Information disclosure | Logging | mitigate | OAuth handlers log structured error categories only; auth codes, ID tokens, access tokens, and client secrets are not logged or rendered. Evidence: `handlers_social.go` and generic provider UI error constants. | closed | +| T-08-08 | Session fixation | Social callback session issuance | mitigate | Successful provider callback creates a normal server-managed Xtablo session cookie through `auth.Store.Create` and `auth.SetSessionCookie`. Evidence: `GoogleCallbackHandler`, session tests. | closed | +| T-08-09 | CSRF / spoofing | Apple callback | mitigate | Apple callback is not mounted after UAT scope change; direct `/auth/apple/start` and `/auth/apple/callback` return 404. Evidence: `router.go`, `TestAppleRoutesAreDisabled`. | closed | +| T-08-10 | Spoofing / tampering | Apple ID token | mitigate | Apple sign-in is disabled in the product surface; no Apple ID token is accepted by mounted routes. Evidence: `router.go`, `cmd/web/main.go`, `TestAppleRoutesAreDisabled`. | closed | +| T-08-11 | Information disclosure | Apple client secret | mitigate | Runtime no longer reads `APPLE_*` env vars or generates Apple client secrets. Evidence: `cmd/web/main.go`, `backend/.env.example`, `backend/README.md`. | closed | +| T-08-12 | Elevation of privilege | Apple first-login name | mitigate | Apple sign-in is disabled, so Apple names cannot affect local identity state. Evidence: unmounted Apple routes and no Apple auth UI. | closed | +| T-08-13 | CSRF middleware interaction | Apple callback / local forms | mitigate | Apple callback route is removed; local POST routes remain under existing CSRF middleware. Evidence: `router.go`, `auth.Mount` ordering tests. | closed | +| T-08-14 | Tampering | Provider buttons | mitigate | Enabled Google control links only to `GET /auth/google/start`; disabled Google control has no `href`; Apple controls are not rendered. Evidence: `auth_login.templ`, `AuthProviderButtonsBlock`, auth page tests. | closed | +| T-08-15 | Information disclosure | UI error copy | mitigate | User-facing provider errors are generic and do not expose token values, codes, claim names, or secrets. Evidence: `providerGenericError`, `providerEmailUnverified`, auth page tests. | closed | +| T-08-16 | CSRF | Auth form regression | mitigate | Provider control is an anchor or disabled button and does not submit email/password forms; existing forms keep CSRF fields and POST handlers. Evidence: `auth_login.templ`, `auth_signup.templ`, `TestLoginProviderButtonsConfigured`, `TestSignupProviderButtonsConfigured`. | closed | +| T-08-17 | Information disclosure | Linked providers view | mitigate | Route is inside `auth.RequireAuth`; query is scoped to current `user.ID`; rendered rows now only include Google. Evidence: `router.go`, `AccountProvidersHandler`, `TestLinkedProviders_UnauthRedirectsToLogin`. | closed | +| T-08-18 | Information disclosure | Env docs | mitigate | `.env.example` uses placeholders only and no real provider secrets; Apple env placeholders were removed after disablement. Evidence: `backend/.env.example`, `backend/README.md`. | closed | +| T-08-19 | Regression drift | Auth/social regression suite | mitigate | Full backend regression passed after Apple disablement and Google/social changes. Evidence: `go test ./... -count=1` run on 2026-05-15, `08-UAT.md` 7/7 passed. | closed | + +--- + +## Accepted Risks Log + +No accepted risks. + +--- + +## Security Audit Trail + +| Audit Date | Threats Total | Closed | Open | Run By | +|------------|---------------|--------|------|--------| +| 2026-05-15 | 19 | 19 | 0 | Codex inline security audit | + +--- + +## Notes + +- Apple implementation primitives still exist in `backend/internal/auth/oauth.go` and unit tests, but the web product surface does not wire Apple config, render Apple controls, or mount Apple auth routes. +- The active provider surface for Phase 8 is Google only. + +--- + +## Sign-Off + +- [x] All threats have a disposition (mitigate / accept / transfer) +- [x] Accepted risks documented in Accepted Risks Log +- [x] `threats_open: 0` confirmed +- [x] `status: verified` set in frontmatter + +**Approval:** verified 2026-05-15