# SEC-004 canonical Microsoft identity verification

Date: 2026-08-02

Status: `IMPLEMENTED — NOT VERIFIED`. Repository implementation, synthetic controller tests,
frontend component tests, build, migration SQL and disposable SQLite upgrade checks pass. Real
Microsoft, browser, SMTP, MariaDB execution and production legacy inventory remain blocked.

## Implemented trust path

- Microsoft account ownership is the normalized GUID pair (`MicrosoftTenantId`,
  `MicrosoftObjectId`) with a unique composite database index.
- Exchange and conflict lookup use only that pair. `MicrosoftSubject` is no longer written and
  provider email is metadata only; a matching local email returns a safe conflict instead of a link.
- New Microsoft registration stores the pair immediately, leaves application email unconfirmed,
  and obeys the verification-required 202/no-session gate.
- Explicit link requires the authenticated local user's current password. Link and unlink revoke
  all sessions/trusted devices; passwordless unlink is refused as a last-credential guard.
- Legacy fields remain null-canonical evidence. A single eligible candidate receives a
  purpose-bound proof at its confirmed application email. Confirmation requires that proof plus a
  fresh Microsoft token for the exact same (`tid`, `oid`) pair. Multiple or unconfirmed candidates
  require operator-assisted recovery.
- Frontend MSAL authority uses the same `AUTH_MICROSOFT_TENANT` value passed at build time.

## Evidence

- Focused auth controller tests: 34/34 passed.
- Full backend suite: 507/507 passed.
- Full frontend suite: 44 suites, 152/152 passed; production build passed.
- SQLite migration SQL uses two nullable `TEXT` columns and a unique composite index.
- MariaDB migration SQL uses two nullable `varchar(36)` columns and the same unique index.
- Disposable SQLite legacy rehearsal:
  - two rows with duplicate legacy subject/email values remained unchanged and null-canonical;
  - migration applied without backfill;
  - first canonical pair assignment succeeded;
  - duplicate pair assignment failed with SQLite unique-constraint exit 19.
- Production Compose interpolation includes the same tenant mode in backend configuration and the
  frontend build argument.

## Blocked checks and residual risk

- No real Microsoft token/account was used. Issuer/tenant/signature behavior is covered by the
  SEC-003 signed-token tests; exchange/link/recovery use mocked tenant-qualified principals.
- The existing browser policy blocker was not retried. No browser workflow, popup, accessibility,
  mobile or screenshot claim is made.
- No SMTP sink was available, so the complete emailed recovery link was not exercised end to end.
- MariaDB SQL generation passed, but no disposable MariaDB execution environment was available.
- Production legacy counts and collision groups are unknown. Microsoft production enablement must
  remain gated until the counts-only inventory in `deploy/README.md` and migration/version-skew
  checks pass.
- Legacy rows with multiple candidates or unavailable/unconfirmed app email deliberately require
  operator verification; no automated merge is attempted.

## Rollback

Disable Microsoft sign-in by clearing its client ID, roll back application binaries, and retain the
additive columns and legacy evidence. Do not restore subject-only or email auto-linking. The migration
`Down` is appropriate only before canonical pair data is relied upon.
