Skip to content

CredentialOps, ServiceOps and LoginOps have the same queued-closure cancellation race as #85 #87

Closed

Description

@barelyworkingcode

Problem

The #85 defect exists in sibling ops too. Each passes a cancellable context to Queue.Do (via runQueued) and then reads or relies on the closure's outputs:

  • CredentialOps.Mint (cmd/relay/credential_ops.go): the same bug as RotateToken. The mint commits and its issuance is recorded, but a cancelled caller gets ctx.Err(), so the plaintext credential is lost. cred and plaintext also race with the worker.
  • Race on their outputs: CredentialOps.Revoke (removed), and ServiceOps Register, Create, Update and SaveConfigFile. Register's closure writes the named err directly.
  • Misreport but don't race: LoginOps.MintBootstrap and passkey revoke report a committed mint or revoke as failed.

Root cause

These use runQueued (Queue.Do) where runCommitted (Queue.DoCommitted) exists for this case.

Fix

Switch them to runCommitted, with -race tests first in the style of project_ops_commit_race_test.go.

Found in the review of #85.

Activity

  1. added
    needs-reviewAwaiting owner review before work starts
    and removed
    needs-reviewAwaiting owner review before work starts
    on Sep 26, 2026
  2. barelyworkingcode commented on Sep 26, 2026

    @barelyworkingcode
    OwnerAuthor

    Contract

    Approved by the coordinator. This is the same fix as #85.

    Change. Add runCommitted to CredentialOps (cmd/relay/credential_ops.go) and ServiceOps (cmd/relay/service_ops.go), shaped exactly like ProjectOps.runCommitted / LoginOps.runCommitted: nil Queue means run directly, otherwise Queue.DoCommitted. Then switch o.runQueued(ctx, …) to o.runCommitted(ctx, …) in exactly these eight methods:

    • CredentialOps.Mint: the one that matters, since a cancelled mint loses the plaintext;
    • CredentialOps.Revoke;
    • ServiceOps.Register, Create, Update, SaveConfigFile;
    • LoginOps.MintBootstrap and LoginOps.RevokePasskey (they misreport; LoginOps.runCommitted already exists).

    The calls that already pass context.Background() are unchanged, as are all signatures, return tuples, gates, audit and wire shapes. The queue structural test already accepts runCommitted. A step cancelled while still queued now runs; that is accepted, and #88 covers it.

    Invariant for Mint and MintBootstrap: outside a deliberate audit-failure withhold path, a successful call returns the secret that was stored. After a cancellation the caller either receives it or nothing was stored. There must never be a stored credential or code whose plaintext the caller lost.

    Tests (red first, -race). New file cmd/relay/ops_commit_race_test.go, one table-driven test in the style of project_ops_commit_race_test.go (hook store, real config.NewCommandQueue(1), cancel after the step is queued, then release). One row per method: it returns the committed result and the store agrees. Rows that race only need to be red or flagged by the race detector on the current code.

    Files. Dev: credential_ops.go, service_ops.go, login_ops.go. Test writer: cmd/relay/ops_commit_race_test.go (new).

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions