Skip to content

fix(secrets): DPAPI legacy migration can clobber a concurrent rotation #486

Closed

Description

@basiliskus

Found while reviewing #464 at commit c46016c. Not a merge blocker; filing so it is tracked rather than lost in the review thread. Related to #461 (Windows support).

The keychain-windows legacy-plaintext migration runs on the read path and finishes with an unconditional mv -f over the key's file, using the value captured before the PowerShell encrypt subprocess ran. Nothing re-checks that the file is still the unmarked legacy content that was read. If an acq secret set or rotation lands inside that window (a real powershell.exe -Command startup takes hundreds of milliseconds to seconds), the freshly rotated value is silently replaced by the old one.

Reproduced with a stubbed powershell.exe whose encrypt step sleeps:

reader start (legacy plaintext on disk, encrypt takes ~2s)
writer      -> stores NEW-rotated-token
immediately after writer: [NEW-rotated-token]
reader done -> returns [OLD-legacy-token]
FINAL store content:      [OLD-legacy-token]   <-- rotation silently reverted

Scope is narrow and self-extinguishing: it only affects the first read of a key that still holds a legacy plaintext value, it requires concurrency, and once the key is migrated the path never runs again for that key. The stale value is also well-formed, so the post-create ensure_valid_key gate catches it, but it presents as a confusing auth failure against a key the user just rotated.

Suggested fix: before the rename, re-check that the target file is still unmarked and still byte-identical to what was read; skip the migration otherwise.

Activity

  1. mogul commented on Oct 3, 2026

    @mogul
    Contributor

    Verified fixed by #464. The merged code re-checks that the target file is still the exact unmarked legacy plaintext before renaming the migrated DPAPI envelope, and skips migration if a concurrent writer changed it. The PR also added a regression test: keychain-windows: migration does not clobber a rewrite that lands mid-encryption.

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions