Skip to content

security(H16): rust-client rotate-sharing-key flow + classified share/pull (re-target of #80 after #81 merged)#82

Merged
tolgaergin merged 1 commit into
mainfrom
security/h8-h16-ws3-rotate-sharing-key-v2
May 21, 2026
Merged

security(H16): rust-client rotate-sharing-key flow + classified share/pull (re-target of #80 after #81 merged)#82
tolgaergin merged 1 commit into
mainfrom
security/h8-h16-ws3-rotate-sharing-key-v2

Conversation

@tolgaergin

Copy link
Copy Markdown
Contributor

Re-opened from #80 after #79's replacement #81 merged. Identical content rebased onto main with the env_vault.rs test-conflict resolved (both env_share_refuses_force_flag and env_rotate_sharing_key tests preserved).

…/pull

Workstream 3 PR #4 (the final WS3 rust-client slice). Adds the
interactive `lpm env rotate-sharing-key` command, migrates the silent
`ensure_public_key` callers in `share` / `pull --org` to the new
classifier path from PR #3, and wires the Workstream 2 CLI step-up
prompt as the reauth primitive for both flows.

Server-side WS3 gates (a-package-manager #30) require a step-up proof
for public-key writes and refuse silent overwrites; this PR makes the
rust-client side of that contract explicit:

  - "Matches" → continue
  - "NeedsInitialSet" → prompt for step-up (vault:public-key:set),
    upload local key, continue
  - "RotationRequired" → STOP, refuse to silently overwrite, point
    the user at `lpm env rotate-sharing-key`

New surfaces:

* `crates/lpm-vault/src/sync.rs`
  - `CliStepUpPolicy` + `discover_cli_step_up_policy()` — GETs the
    server's step-up policy so the CLI knows whether to prompt for
    password, password+TOTP, or refuse outright.
  - `CliStepUpCredential` + `mint_cli_step_up_proof()` — POSTs the
    credential and returns the proof JWT for the X-LPM-Step-Up-Proof
    header.
  - `PendingPublicKey` + `create_pending_x25519_keypair` /
    `read_pending_x25519_keypair` / `promote_pending_x25519_keypair`
    / `discard_pending_x25519_keypair`. File-backed
    `~/.lpm/.x25519_key.pending` slot kept distinct from the live
    slot so a crash between server-side upload and local promotion
    leaves a recoverable state (the next rotate-sharing-key
    invocation detects the matching pending key and finishes the
    promotion).
  - `should_use_file_backed_x25519_keypair(force_file, live_key_exists)`
    selector — the macOS loader now prefers the file-backed slot
    when a live file is present, so promotion's file write actually
    takes effect on the next read instead of being silently replaced
    by a fresh keychain key. Regression-pinned at
    `x25519_backend_selection_uses_live_file_after_rotation_without_force_env`.
    [Fix authored by GPT after the initial round caught the macOS
    keychain-fallthrough bug.]

* `crates/lpm-vault/src/keychain.rs`
  - `delete_x25519_keypair()` — best-effort macOS keychain clear, used
    by promotion so subsequent reads observe the new live file.

* `crates/lpm-cli/src/step_up.rs` (new module)
  - `request_cli_step_up_proof(registry_url, auth_token, scope)` —
    cliclack-driven prompts (password / password+TOTP), strict
    non-TTY refusal so a CI environment can't blunder into a hung
    prompt or accept hostile piped input.

* `crates/lpm-cli/src/commands/env.rs`
  - New `rotate-sharing-key` dispatcher arm + `env_rotate_sharing_key`
    implementation. Refuses non-TTY (and explicit `--yes`) at the
    door. Crash-recovery branch detects a matching pending key on
    server and finishes promotion without a second rotation. Blast-
    radius warning + typed-ROTATE confirmation before any prompt.
    On success, reports the wrapped-key invalidation counts the
    server returns.
  - `ensure_sharing_key_ready_for_org_op()` classify-then-act helper
    used by both the share and the org-pull paths. Refuses
    `RotationRequired` with a remediation hint that names the rotate
    flow. Prompts step-up + uploads on `NeedsInitialSet`. The prior
    `ensure_public_key()` silent-upload path was the headline H16
    silent-overwrite vector; this is the client side of the WS3 gate.
  - `unknown vars action` help text now lists `rotate-sharing-key`.

Tests:

* lpm-vault inline (`crates/lpm-vault/src/sync.rs`) — 10 new across
  step-up clients (discover password/unavailable/non-2xx; mint
  password body shape, totp body shape, error envelope), pending-key
  lifecycle (create→read→promote round-trip, discard preserves live,
  promote-with-no-pending is an explicit error), and the macOS
  backend selector regression test from GPT's fix.

* lpm-workflows (`tests/workflows/tests/env_vault.rs`) — 2 new
  pinning the non-TTY refusal for `rotate-sharing-key` (with and
  without `--yes`). Both run from `cargo test`'s pipe-backed stdin
  so the refusal must fire at TTY-detect time, before any network
  or pending-key side effect.

Local gate: `cargo fmt --check` clean, `cargo clippy --workspace
--all-targets -- -D warnings` clean, `cargo nextest run --workspace
--exclude lpm-integration-tests` — 7374/7374 pass (12 new vs PR #79
baseline of 7362).

Branched off `security/h8-h16-ws3-public-key-vault-crate` (PR #79).
Auto-retargets to `main` when #79 merges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tolgaergin tolgaergin force-pushed the security/h8-h16-ws3-rotate-sharing-key-v2 branch from 2309c28 to 59753cc Compare May 21, 2026 17:38
@tolgaergin tolgaergin merged commit bc4cd40 into main May 21, 2026
8 checks passed
@tolgaergin tolgaergin deleted the security/h8-h16-ws3-rotate-sharing-key-v2 branch May 21, 2026 18:02
@tolgaergin tolgaergin mentioned this pull request May 22, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant