security(H16): rust-client rotate-sharing-key flow + classified share/pull (re-target of #80 after #81 merged)#82
Merged
Conversation
…/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>
2309c28 to
59753cc
Compare
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).