Skip to content

OAUTH-001B9 — Refuse stale Strava disconnect cleanup #608

Closed

Description

@daliu

Parent: #88

Officer impact: None now. After a future protected Functions release, an older disconnect may return the existing unknown-result instruction instead of erasing a newer Strava reconnect.
Officer documentation: None — this is a server-only source boundary. The existing SOURCE ONLY / NOT LIVE unknown-disconnect procedure already tells the member to refresh and stop when the result cannot be confirmed; no officer step, screen, permission, deployment path, or verified live behavior changes.
Deployment evidence: Source, tests, merge, website, runmprc.com, Firebase, Strava/provider, production data, and live behavior are all unverified at issue creation.

Problem

The trusted disconnect path reads secret version A, may wait on Strava revocation, and then blindly batch-deletes the current secret and connection records. A concurrent refresh or reconnect can commit version B during that wait. The older disconnect then erases B and returns success, losing the only local credential for provider access that may still be active.

Invariant and allowed transitions

Capture the initial Firestore secret snapshot's existence and immutable update time before provider work. After admitted provider success, or immediately when no admissible provider token exists, use one Firestore transaction to reread the secret before deleting both local records.

  • Current secret exists at the exact initial update time: atomically delete the secret and connection and return success.
  • Current secret exists but is newer, including byte-identical replacement: stage no delete, preserve both records, and return the existing fixed unknown result with one fixed redacted cleanup marker.
  • Initial secret was absent but a secret now exists: treat it as a newer connection, preserve both records, and return the fixed unknown result.
  • Current secret is absent: atomically delete any stale connection and return success; a later reconnect wins by Firestore serialization.
  • Transaction conflict retry: reread the current version without repeating provider work; a newer version is preserved.
  • Transaction construction, read, version access/comparison, either delete staging, commit, or acknowledgement failure: preserve the existing fixed redacted cleanup boundary. A lost acknowledgement may mean both records were deleted; retry remains convergent.
  • Never log or expose tokens, snapshot versions, provider bodies, raw failures, UIDs, paths, or record data.

Atomic scope

  • functions/strava.js: retain only the admitted initial disconnect snapshot/version and replace the final blind batch cleanup with compare-before-delete transaction logic.
  • functions/strava.test.js: add minimal immutable snapshot-update-time support to the Firestore fake; add a trustworthy named OAUTH-001B9 RED/race block; mechanically update only directly affected OAUTH-001B7 cleanup assertions.
  • SECURITY.md: update only the existing RISK-024 current-truth row with the source-only B9 fence and its residuals.

Acceptance

  • Record a focused test-only RED result against the exact base.
  • Prove a reconnect committed while token A's revoke is pending remains byte-for-byte intact and returns the fixed unknown result.
  • Prove a transaction conflict retries, observes B, and applies no delete.
  • Prove a byte-identical replacement with a newer update time is preserved.
  • Prove absent-to-new and malformed-to-repaired races preserve the newer pair.
  • Prove unchanged, already-absent, provider-failure, pre-apply failure, and lost-acknowledgement/retry behavior.
  • Prove provider work occurs at most once and never inside the transaction.
  • Run focused and full relevant checks and complete independent security and backup-officer review.
  • Record exact merge and deployment-surface evidence before closure.

Migration impact: None. Firestore's existing document update time is the compare-and-delete fence; no stored field, backfill, or production read is added.

Exclusions / stop conditions

Do not expand into client response-shape validation (reserved for a separate B10 child), provider-call serialization, a durable generation/tombstone/audit worker, provider/local acknowledgement reconciliation, response-body byte limits, scope/account policy, other Strava handlers, packages, Rules, workflows, officer-guide changes, Firebase deployment, provider configuration/calls outside synthetic mocks, real tokens, production data, or live verification. Do not close #88 from this child alone.

Stop if a competing timestamped claim appears, the exact disconnect hunks change first, or correctness requires a durable stored generation/schema migration instead of this bounded update-time fence.

Issue creation is not a claim.

Activity

  1. added
    bugSomething isn't working
    priority:P0Launch blocker or urgent security risk
    type:securitySecurity or privacy boundary
    area:authAuthentication and authorization
    area:firebaseFirebase services and data
    size:SSmall focused issue
    type:testingTest infrastructure and quality gates
    area:privacyPersonal data, consent, minimization, retention, and privacy operations
    status:in-progressClaimed and actively being implemented
    on Aug 2, 2026
  2. self-assigned this
    on Aug 2, 2026
  3. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    CLAIMED by /root at 2026-08-02T06:36:41Z.

    Officer impact: None now. A future protected Functions release may preserve a newer reconnect and return the existing unknown-result instruction instead of erasing it.
    Officer documentation: None — the existing SOURCE ONLY / NOT LIVE unknown-disconnect procedure remains accurate and no officer task or verified live behavior changes.
    Deployment evidence: None yet. No Firebase, Strava/provider, website, production-data, or live action is authorized by this claim.

    No source edit began before this issue, branch, assignment, status label, and timestamped claim. Stop and coordinate before touching the exact owned hunks.

  4. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    RED evidence — 2026-08-02

    Exact base: 24ec0e5789c0ff26cba49371d298aea5c0cdf3ce

    Test-only binary diff SHA-256: 1d8477963d87cc555147f7ac961ddf29ffcf696846714b14be254cd41177fe03

    Focused Node 20 command: npm --prefix functions run test:run -- --runInBand strava.test.js -t "OAUTH-001B9 stale disconnect cleanup fence"

    Expected result: 1 intended failure, 711 unrelated tests skipped. The paused synthetic revoke observed token A; the test committed a made-up replacement secret and connection B before resolving provider success. Exact old source fulfilled instead of returning the fixed unknown result, then erased B. The failure occurred at Expected Strava request to fail.

    Synthetic values and mocks only. No Firebase project, Strava/provider call, website, production data, deployment, or live action.

  5. added a commit that references this issue on Aug 2, 2026
    9d8bfaa
  6. removed their assignment
    on Aug 2, 2026
  7. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    COMPLETE / CLOSED / CLAIM RELEASED

    Outcome: merged PR #609 now refuses stale Strava disconnect cleanup. A newer reconnect, including a byte-identical replacement with a newer Firestore update time, is preserved and the caller receives the existing fixed unknown result.

    Exact evidence:

    • Base: 24ec0e5789c0ff26cba49371d298aea5c0cdf3ce
    • Reviewed head: 0baf701e6dad16f5d5a6f842001c2c3216921d88
    • Reviewed binary diff SHA-256: 645fd89eb771011c634ee37677471a7b1fe59ba7b46d04415fbac77e1d6f005c
    • Squash merge / exact release SHA: 9d8bfaae0a4f9e573053b4035954e933ae626875
    • Reviewed-head and merge trees: 613e20ae3ba10b2f3b7085d06ed529486b82f6c6 (identical)
    • PR CI 30736839293: all five jobs passed
    • Exact-main CI 30736937726: all five jobs passed
    • Independent exact-head runtime/security, test/fake, and backup-officer reviews: GO
    • Remote topic branch: absent
    • Issue state: completed; in-progress label and assignee removed

    Officer impact: None now. This is protected Function source only and is not deployed.

    Officer documentation: None — the current no-terminal Strava disconnect procedure already covers every unconfirmed result with preserve/refresh/stop/escalate steps. No officer action, visible wording, page structure, permission, data flow, account ownership, or deployment topology changed; no Mermaid update was required.

    Deployment evidence checked 2026-08-02:

    • Source changed, tests passed, and code merged.
    • PR Netlify preview 6a6eea20c6fa380008f2371a was ready for the exact head; published_at was null.
    • Automatic main attempt 6a6eeae42c8dae0008c35f63 stopped in error with published_at null.
    • GitHub deployments for both reviewed head and exact main were [].
    • The public runmprc.com marker remained the bounded WEB-002A — Publish bounded Shop and Events permissions containment #473 release: control 9ad6837756cdd409d296009fde5082eeeae5c059, source 39ab8649df411262c8109a3c81a57bc38f1e168b, tree d76b496cdc5e79015bc048cb961758abbe88b9ce.
    • Website published: no. Firebase deployed: no. Strava provider configured/called: no. Production account/data changed: no. Checkout/payment/live behavior: not involved and not verified.

    Residuals remain explicit in SECURITY.md: provider calls are unserialized; provider/local and lost-acknowledgement reconciliation, connection-only writers, client confirmation admission, runtime IAM/encryption, deployment, and live behavior remain open.

    Parent #88 remains open. No path or outcome remains claimed by #608.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:authAuthentication and authorizationarea:firebaseFirebase services and dataarea:privacyPersonal data, consent, minimization, retention, and privacy operationsarea:stravaStrava integrationbugSomething isn't workingpriority:P0Launch blocker or urgent security risksize:SSmall focused issuetype:reliabilityReliability and recoverytype:securitySecurity or privacy boundarytype:testingTest infrastructure and quality gates

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions