diff --git a/.claude/commands/bump-rust-sdk.md b/.claude/commands/bump-rust-sdk.md new file mode 100644 index 0000000000..131fa874c9 --- /dev/null +++ b/.claude/commands/bump-rust-sdk.md @@ -0,0 +1,41 @@ +--- +description: Bump sdk-internal Rust crate dependencies in util/RustSdk to align with a Bitwarden clients release +argument-hint: [clients-release-tag] +allowed-tools: Read, Write, Edit, Glob, Grep, Bash(cargo *), Bash(dotnet *), Bash(git *), Bash(gh *) +--- + +Bump the sdk-internal Rust crate dependencies in `util/RustSdk/rust/Cargo.toml` to align with +the Bitwarden clients production release specified by `$ARGUMENTS`. If no release tag is given, +determine the latest `web-v*` release tag from `bitwarden/clients`. + +Invoke the `bump-rust-sdk` skill using the task tool for the full methodology, API surface reference, and worked examples. + +## Required Context + +Before starting, read these files to understand the current state: + +- `util/RustSdk/rust/Cargo.toml` — current rev pins +- `util/RustSdk/rust/src/*.rs` — current API usage +- `.claude/skills/bump-rust-sdk/references/api-surface.md` — documented API surface +- `.claude/skills/bump-rust-sdk/references/methodology.md` — detailed process + +## Execution + +Follow the skill's process in order: + +1. **Identify target** — Find the `@bitwarden/sdk-internal` NPM version at the release tag +2. **Map to git SHA** — Query the GitHub Actions API for the publish workflow run number +3. **Analyze breaking changes** — Compare old rev to new rev across the three crates, using the API surface reference +4. **Apply changes** — Update Cargo.toml, fix compilation errors, handle deprecations +5. **Build and verify** — `cargo build`, `cargo test`, `dotnet test test/SeederApi.IntegrationTest/`, `cargo fmt --check` +6. **Human verification** — Present the SeederUtility and SeederApi test commands to the human. **Do NOT run these yourself.** Wait for the human to confirm. +7. **Regenerate API surface** — Read all `.rs` files in `util/RustSdk/rust/src/` and update `.claude/skills/bump-rust-sdk/references/api-surface.md` to reflect the current imports, types, and traits. This step is mandatory — the reference must always match the actual code. + +## Important Rules + +- All three crate rev pins MUST be the same SHA +- Do NOT make unrelated formatting or style changes to the Rust source files +- Do NOT run SeederUtility or SeederApi yourself — the human performs all end-to-end testing +- Do NOT skip the API surface regeneration step — a Stop hook will block if it is missed +- `util/RustSdk/NativeMethods.g.cs` should NOT change — verify with `git diff` after build +- `util/RustSdk/rust/Cargo.lock` will change and must be included alongside the other changes diff --git a/.claude/hooks/README.md b/.claude/hooks/README.md new file mode 100644 index 0000000000..74aa134b7d --- /dev/null +++ b/.claude/hooks/README.md @@ -0,0 +1,64 @@ +# Claude Code Hooks + +All hooks are Stop hooks — they fire when Claude finishes responding and check +whether documentation or references need updating based on what was changed. + +## Configuration + +Register hooks in `.claude/settings.local.json` — not `settings.json`. Local settings are gitignored, keeping your personal hook configuration out of source control. + +## Requirements + +- `jq` must be installed (`brew install jq`) +- Must be run from within a git repository + +## Testing & Debugging + +- **Verbose mode**: Press `Ctrl+O` in Claude Code to see hook execution details +- **Debug mode**: Run `claude --debug` for full execution logging + +## Disabling + +- Use the `/hooks` menu in Claude Code to toggle individual hooks +- Or set `"disableAllHooks": true` in `.claude/settings.local.json` + +--- + +## seeder-docs-check.sh + +**Event:** Stop + +**Purpose:** Reminds developers to update Seeder documentation when code in +`util/Seeder/`, `util/SeederApi/`, or `util/SeederUtility/` was modified but no +`.md` files in those directories were touched. + +**How it works:** + +1. Runs `git diff` to detect all changed files +2. If non-markdown files were changed under a Seeder project AND no `.md` files + in any of the three Seeder projects were modified, blocks the stop with a + reminder (intentionally cross-project — a doc update anywhere in the Seeder + subsystem satisfies the check) +3. The reminder lists all `.md` files in the affected projects (discovered dynamically) +4. On the next stop, `stop_hook_active` is true, so the hook allows through +5. Result: one reminder per stop, then the developer decides + +--- + +## rust-sdk-surface-check.sh + +**Event:** Stop + +**Purpose:** Ensures the RustSdk API surface reference stays current when the +sdk-internal dependency rev is bumped. Prevents `.claude/skills/bump-rust-sdk/references/api-surface.md` +from going stale. + +**How it works:** + +1. Runs `git diff` to detect all changed files +2. If `util/RustSdk/rust/Cargo.toml` was modified BUT + `.claude/skills/bump-rust-sdk/references/api-surface.md` was NOT, blocks the + stop with a reminder to regenerate the API surface inventory +3. On the next stop, `stop_hook_active` is true, so the hook allows through +4. Result: one reminder per stop — Claude reads the `.rs` source files and + regenerates the reference diff --git a/.claude/hooks/rust-sdk-surface-check.sh b/.claude/hooks/rust-sdk-surface-check.sh new file mode 100755 index 0000000000..dab2df21ad --- /dev/null +++ b/.claude/hooks/rust-sdk-surface-check.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# rust-sdk-surface-check.sh +# Stop hook: reminds developers to update the RustSdk API surface reference +# when Cargo.toml rev pins were modified but api-surface.md was not touched. +# +# Behavior: blocks Claude from stopping exactly once with a reminder. +# On the second stop (stop_hook_active=true), allows through. + +set -euo pipefail + +INPUT=$(cat) + +# Guard: if a Stop hook already blocked this turn, allow through. +STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false') +if [[ "$STOP_HOOK_ACTIVE" == "true" ]]; then + exit 0 +fi + +CWD=$(echo "$INPUT" | jq -r '.cwd') + +# Gather all changed files (staged, unstaged, and untracked) relative to repo root. +DIFF_HEAD=$(git -C "$CWD" diff --name-only HEAD 2>/dev/null || true) +UNTRACKED=$(git -C "$CWD" ls-files --others --exclude-standard 2>/dev/null || true) +ALL_CHANGED=$(printf "%s\n%s" "$DIFF_HEAD" "$UNTRACKED" | sort -u | grep -v '^$' || true) + +if [[ -z "$ALL_CHANGED" ]]; then + exit 0 +fi + +# Check if the RustSdk Cargo.toml was modified. +if ! echo "$ALL_CHANGED" | grep -q '^util/RustSdk/rust/Cargo.toml$'; then + exit 0 +fi + +# Check if the API surface reference was already updated. +if echo "$ALL_CHANGED" | grep -q '^\.claude/skills/bump-rust-sdk/references/api-surface.md$'; then + exit 0 +fi + +REASON="util/RustSdk/rust/Cargo.toml was modified but the API surface reference was not updated. Read all .rs files in util/RustSdk/rust/src/ and regenerate .claude/skills/bump-rust-sdk/references/api-surface.md to reflect the current imports, types, and traits used from bitwarden-core, bitwarden-crypto, and bitwarden-vault." + +jq -n --arg reason "$REASON" '{ "decision": "block", "reason": $reason }' diff --git a/.claude/hooks/seeder-docs-check.sh b/.claude/hooks/seeder-docs-check.sh new file mode 100755 index 0000000000..1138aacf83 --- /dev/null +++ b/.claude/hooks/seeder-docs-check.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# seeder-docs-check.sh +# Stop hook: reminds developers to update Seeder documentation when +# Seeder code was modified but no documentation files were touched. +# +# Behavior: blocks Claude from stopping exactly once with a reminder. +# On the second stop (stop_hook_active=true), allows through. + +set -euo pipefail + +INPUT=$(cat) + +# Guard: if a Stop hook already blocked this turn, allow through. +STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false') +if [[ "$STOP_HOOK_ACTIVE" == "true" ]]; then + exit 0 +fi + +CWD=$(echo "$INPUT" | jq -r '.cwd') + +# Gather all changed files (staged, unstaged, and untracked) relative to repo root. +DIFF_HEAD=$(git -C "$CWD" diff --name-only HEAD 2>/dev/null || true) +UNTRACKED=$(git -C "$CWD" ls-files --others --exclude-standard 2>/dev/null || true) +ALL_CHANGED=$(printf "%s\n%s" "$DIFF_HEAD" "$UNTRACKED" | sort -u | grep -v '^$' || true) + +if [[ -z "$ALL_CHANGED" ]]; then + exit 0 +fi + +# Check which Seeder projects have non-markdown code changes. +SEEDER_CODE_CHANGED=false +SEEDER_PROJECTS_CHANGED=() + +for project in "util/Seeder/" "util/SeederApi/" "util/SeederUtility/"; do + if echo "$ALL_CHANGED" | grep -q "^${project}" && \ + echo "$ALL_CHANGED" | grep "^${project}" | grep -qv '\.md$'; then + SEEDER_CODE_CHANGED=true + SEEDER_PROJECTS_CHANGED+=("$project") + fi +done + +if [[ "$SEEDER_CODE_CHANGED" == "false" ]]; then + exit 0 +fi + +# Check if any Seeder .md files were already modified. +if echo "$ALL_CHANGED" | grep -qE '^util/(Seeder|SeederApi|SeederUtility)/.*\.md$'; then + exit 0 +fi + +# Dynamically discover all .md files in each modified project. +DOCS_LIST="" +for project in "${SEEDER_PROJECTS_CHANGED[@]}"; do + while IFS= read -r md_file; do + DOCS_LIST="${DOCS_LIST}\n - ${md_file}" + done < <(find "$CWD/$project" -name "*.md" | sed "s|^$CWD/||" | sort) +done + +REASON=$(printf "Seeder code was modified but no Seeder documentation was updated. Please check whether any of these docs need updating:%b\n\nIf the docs are already accurate, let the user know you verified them." "$DOCS_LIST") + +jq -n --arg reason "$REASON" '{ "decision": "block", "reason": $reason }' diff --git a/.claude/skills/bump-rust-sdk/SKILL.md b/.claude/skills/bump-rust-sdk/SKILL.md new file mode 100644 index 0000000000..529a55f4c2 --- /dev/null +++ b/.claude/skills/bump-rust-sdk/SKILL.md @@ -0,0 +1,141 @@ +--- +name: bump-rust-sdk +description: This skill should be used when the user asks to "bump the Rust SDK", "update sdk-internal", "bump bitwarden-crypto", "update RustSdk dependencies", "align server SDK with clients", or needs to update the bitwarden/sdk-internal git rev pins in util/RustSdk/rust/Cargo.toml. Provides the methodology for mapping client NPM versions to git commit SHAs, analyzing breaking changes, auditing the API surface, and verifying the bump end-to-end. +--- + +# Bump sdk-internal Rust Crate Dependencies + +## Overview + +The server's `util/RustSdk/rust/Cargo.toml` pins three crates from the `bitwarden/sdk-internal` +repository by git rev: `bitwarden-core`, `bitwarden-crypto`, and `bitwarden-vault`. These must +be periodically bumped to stay aligned with the Bitwarden client applications. + +The RustSdk is used by the Seeder to produce cryptographically correct Protected Data for +integration testing. It is NOT part of the production server runtime. + +## Key Challenge: NPM-to-Git-Rev Mapping + +The clients consume sdk-internal via **NPM packages** (`@bitwarden/sdk-internal`), while the +server consumes it via **Rust git rev pins**. The NPM version (e.g., `0.2.0-main.522`) does not +directly correspond to a git tag — it encodes a GitHub Actions **workflow run number**. + +### Version Format + +``` +0.2.0-main.522 +│ │ │ +│ │ └── GitHub Actions run number for publish-wasm-internal workflow +│ └── Branch name (/ replaced with -) +└── Base version from sdk-internal +``` + +### How to Find the Git Rev + +1. Determine the target NPM version from the clients repo (see Step 1 below) +2. Find the `Publish @bitwarden/sdk-internal` workflow ID in the sdk-internal repo +3. Query the GitHub Actions API for the specific run number +4. Extract the `head_sha` — that is the git rev to pin in Cargo.toml + +The specific API queries are documented in `references/methodology.md`. + +## Process Overview + +### Step 1: Identify Target Version + +Determine which sdk-internal version to target. Check the latest production release tag from +`bitwarden/clients` (e.g., `web-v2026.2.0`): + +```bash +cd /path/to/clients +git show web-v2026.2.0:package.json | grep sdk-internal +``` + +This gives the NPM version (e.g., `0.2.0-main.522`). Extract the run number (522). + +### Step 2: Map NPM Version to Git SHA + +Query the GitHub Actions API to find the commit that produced that NPM build. See +`references/methodology.md` for the exact commands. + +### Step 3: Analyze Breaking Changes + +Compare the current pinned rev against the target rev, focusing on the three crates: + +```bash +cd /path/to/sdk-internal +git log --oneline .. -- crates/bitwarden-core crates/bitwarden-crypto crates/bitwarden-vault +``` + +Cross-reference each commit against the API surface documented in `references/api-surface.md`. + +### Step 4: Apply Changes + +1. Update `Cargo.toml` — bump all three rev pins to the same SHA +2. Fix any compilation errors from breaking changes (type renames, new struct fields, etc.) +3. Add `#[allow(deprecated)]` for any newly-deprecated APIs (with a comment explaining why) + +### Step 5: Build and Verify (Claude) + +```bash +cd util/RustSdk/rust +cargo build # Must compile cleanly +cargo test # All tests must pass (roundtrip test is critical) +cargo fmt --check # Formatting must be clean +git diff ../NativeMethods.g.cs # FFI signatures should be unchanged +``` + +Also run the C# integration tests: + +```bash +dotnet test test/SeederApi.IntegrationTest/ +``` + +### Step 6: Human Verification (HUMAN ONLY) + +**Claude does NOT perform this step.** Present these commands to the human engineer and wait +for confirmation before proceeding. + +The human runs SeederUtility and SeederApi to verify Protected Data is correctly produced and +decryptable by the web client. See `references/methodology.md` for the specific test commands +and validation criteria. + +## Security Notes + +- The RustSdk lives in `util/` (test infrastructure), not `src/` (production) +- The server never decrypts Vault Data — zero-knowledge invariant is unaffected +- Aligning with the production client release ensures the Seeder produces Protected Data + using the same cryptographic primitives as real clients +- Review `Cargo.lock` diff for unexpected transitive crypto crate changes (rsa, aes, sha2, etc.) + +## Keeping References Current + +The API surface reference (`references/api-surface.md`) must always reflect the actual code. +Two mechanisms enforce this: + +1. **Post-bump step** — The `/bump-rust-sdk` command includes a mandatory final step to + regenerate `api-surface.md` by reading the actual `*.rs` source files. +2. **Stop hook** — `.claude/hooks/rust-sdk-surface-check.sh` blocks if `Cargo.toml` was + modified but `api-surface.md` was not updated in the same session. + +To regenerate: read all `.rs` files in `util/RustSdk/rust/src/`, extract every `use` statement +from the three bitwarden crates, and rewrite `references/api-surface.md` to match. + +## Additional Resources + +### Reference Files + +- **`references/methodology.md`** — Detailed step-by-step commands including GitHub Actions API + queries, breaking change analysis checklist, human verification commands, and a worked example + from the Feb 2026 bump +- **`references/api-surface.md`** — Complete inventory of types, traits, and functions the RustSdk + imports from each crate, used to assess breaking change impact + +## Files Modified in a Typical Bump + +| File | Change | +| --------------------------------- | -------------------------------------------------- | +| `util/RustSdk/rust/Cargo.toml` | Rev pin update | +| `util/RustSdk/rust/src/*.rs` | Type renames, new struct fields, deprecation fixes | +| `util/RustSdk/rust/Cargo.lock` | Auto-regenerated (commit alongside) | +| `util/RustSdk/NativeMethods.g.cs` | Should NOT change (verify) | diff --git a/.claude/skills/bump-rust-sdk/references/api-surface.md b/.claude/skills/bump-rust-sdk/references/api-surface.md new file mode 100644 index 0000000000..b37b406039 --- /dev/null +++ b/.claude/skills/bump-rust-sdk/references/api-surface.md @@ -0,0 +1,116 @@ +# RustSdk API Surface Inventory + +> **Auto-generated from actual source files.** Last updated: 2026-02-25 +> Pinned rev: `abba7fdab687753268b63248ec22639dff35d07c` + +This documents every type, trait, and function the server's RustSdk imports from the three +sdk-internal crates. Use this to assess breaking change impact when bumping revs. + +**Location:** `util/RustSdk/rust/src/` + +## bitwarden-crypto (primary dependency) + +### Types Used + +| Type | File | Usage | +| ------------------------- | ----------------- | -------------------------------------------------------------------------------------------- | +| `BitwardenLegacyKeyBytes` | lib.rs, cipher.rs | `BitwardenLegacyKeyBytes::from()` — wraps raw key bytes for `SymmetricCryptoKey::try_from()` | +| `HashPurpose` | lib.rs | `HashPurpose::ServerAuthorization` enum variant | +| `Kdf` | lib.rs | `Kdf::PBKDF2 { iterations }` enum variant with `NonZeroU32` | +| `KeyStore` | cipher.rs | `KeyStore::::default()`, `.context_mut()`, `.add_local_symmetric_key()` | +| `MasterKey` | lib.rs | `MasterKey::derive()`, `.derive_master_key_hash()`, `.make_user_key()` | +| `PrivateKey` | lib.rs | `PrivateKey::from_pem()`, `.to_public_key()`, `.to_der()` | +| `PublicKey` | lib.rs | `PublicKey::from_der()` | +| `RsaKeyPair` | lib.rs | Struct literal: `RsaKeyPair { private, public }` | +| `SpkiPublicKeyBytes` | lib.rs | `SpkiPublicKeyBytes::from()` — wraps public key DER bytes | +| `SymmetricCryptoKey` | lib.rs, cipher.rs | `.make_aes256_cbc_hmac_key()`, `::try_from()`, `.to_base64()` | +| `UnsignedSharedKey` | lib.rs | `::encapsulate_key_unsigned()` (deprecated — wrapped with `#[allow(deprecated)]`) | +| `UserKey` | lib.rs | `UserKey::new()`, `.make_key_pair()`, `.0` field access | + +### Traits Used + +| Trait | File | Methods Called | +| ---------------------- | ----------------- | -------------------------------------------------------------------------- | +| `KeyEncryptable` | lib.rs, cipher.rs | `.encrypt_with_key(&key)` — encrypts DER bytes and strings | +| `CompositeEncryptable` | cipher.rs | `.encrypt_composite(&mut ctx, key_id)` — encrypts `CipherView` -> `Cipher` | +| `Decryptable` | cipher.rs | `.decrypt(&mut ctx, key_id)` — decrypts `Cipher` -> `CipherView` | + +## bitwarden-core (minimal dependency) + +| Type | File | Usage | +| ------------------------ | --------- | -------------------------------------------- | +| `key_management::KeyIds` | cipher.rs | Generic type parameter: `KeyStore::` | + +## bitwarden-vault (data model dependency) + +### Production Code + +| Type | File | Usage | +| ------------ | --------- | ----------------------------------------------------- | +| `Cipher` | cipher.rs | Protected Data container (encrypted), deserialized from JSON via serde | +| `CipherView` | cipher.rs | Vault Data in Use (decrypted view), serialized to/from JSON via serde | + +### Test-Only Types + +| Type | File | Usage | +| -------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `CipherRepromptType` | cipher.rs tests | `CipherRepromptType::None` enum variant | +| `CipherType` | cipher.rs tests | `CipherType::Login` enum variant | +| `LoginView` | cipher.rs tests | Struct literal with fields: `username`, `password`, `password_revision_date`, `uris`, `totp`, `autofill_on_page_load`, `fido2_credentials` | + +### CipherView Fields (used in test struct literal) + +The test helper `create_test_cipher_view()` constructs a `CipherView` with these fields: + +`id`, `organization_id`, `folder_id`, `collection_ids`, `key`, `name`, `notes`, `type`, +`login`, `identity`, `card`, `secure_note`, `ssh_key`, `favorite`, `reprompt`, +`organization_use_totp`, `edit`, `permissions`, `view_password`, `local_data`, `attachments`, +`attachment_decryption_failures`, `fields`, `password_history`, `creation_date`, `deleted_date`, +`revision_date`, `archived_date` + +Any new required field added to `CipherView` upstream will break this struct literal. + +## Breaking Change Risk Matrix + +When reviewing upstream commits, prioritize checking for changes to: + +**Critical (compilation failure):** + +- Any type rename or removal listed above +- New required fields on `CipherView`, `Cipher`, or `LoginView` +- Changes to `KeyStore` generic parameters or `context_mut()` method +- Changes to encryption/decryption trait method signatures + +**High (runtime failure):** + +- Changes to `Kdf::PBKDF2` enum variant +- Changes to `HashPurpose::ServerAuthorization` +- Changes to `MasterKey::derive()` or key derivation behavior +- Changes to `UnsignedSharedKey::encapsulate_key_unsigned()` signature + +**Medium (deprecation warnings):** + +- Functions annotated with `#[deprecated]` — suppress with `#[allow(deprecated)]` + and add a comment explaining why and what the migration path is + +**Low (transparent):** + +- Internal implementation changes that don't affect the public API +- New optional fields on structs (serde defaults to `None` for `Option`) +- New methods added to existing types (additive, non-breaking) + +## How to Check for Changes + +For each crate, check the public API exports: + +```bash +cd /path/to/sdk-internal +# bitwarden-crypto public API +git diff .. -- crates/bitwarden-crypto/src/lib.rs crates/bitwarden-crypto/src/keys/mod.rs + +# bitwarden-vault Cipher/CipherView changes +git diff .. -- crates/bitwarden-vault/src/cipher/cipher.rs crates/bitwarden-vault/src/cipher/login.rs + +# bitwarden-core KeyIds +git diff .. -- crates/bitwarden-core/src/key_management/mod.rs +``` diff --git a/.claude/skills/bump-rust-sdk/references/methodology.md b/.claude/skills/bump-rust-sdk/references/methodology.md new file mode 100644 index 0000000000..5a49d1e7c5 --- /dev/null +++ b/.claude/skills/bump-rust-sdk/references/methodology.md @@ -0,0 +1,206 @@ +# Bump sdk-internal: Detailed Methodology + +## Step-by-Step Process + +### 1. Identify the Current Server Pin + +```bash +grep 'rev = ' util/RustSdk/rust/Cargo.toml +``` + +All three crates (`bitwarden-core`, `bitwarden-crypto`, `bitwarden-vault`) must always pin to +the **same rev**. If they don't, something is wrong. + +### 2. Identify the Target Version from Clients + +Determine the latest production release tag from the clients repo: + +```bash +# Check latest web release +gh release list --repo bitwarden/clients --limit 5 | grep web-v + +# Get the sdk-internal NPM version at that tag +cd /path/to/clients +git show :package.json | grep sdk-internal +``` + +Example output: `"@bitwarden/sdk-internal": "0.2.0-main.522"` + +The run number is **522** — the last segment after the branch name. + +### 3. Map NPM Run Number to Git SHA + +The NPM version encodes a GitHub Actions workflow run number, not a git tag. Query the API: + +```bash +# Find the publish workflow ID +gh api "repos/bitwarden/sdk-internal/actions/workflows" \ + --jq '.workflows[] | "\(.id) \(.name)"' | grep -i "Publish.*sdk-internal" + +# Query for the specific run number (replace WORKFLOW_ID and RUN_NUMBER) +gh api "repos/bitwarden/sdk-internal/actions/workflows/WORKFLOW_ID/runs?per_page=100" \ + --jq '.workflow_runs[] | select(.run_number == RUN_NUMBER) | "\(.run_number) \(.head_sha) \(.created_at) \(.head_branch)"' +``` + +The `head_sha` in the output is the git commit to pin in Cargo.toml. + +**If the run is older than 100 runs ago**, paginate: + +```bash +gh api "repos/bitwarden/sdk-internal/actions/workflows/WORKFLOW_ID/runs?per_page=100&page=2" \ + --jq '.workflow_runs[] | select(.run_number == RUN_NUMBER) | ...' +``` + +### 4. Verify the Current Pin (for context) + +```bash +cd /path/to/sdk-internal +git log --oneline -1 +``` + +This shows when the current pin was made and what commit it corresponds to. + +### 5. Analyze Breaking Changes + +List all commits touching the three crates between the old and new revs: + +```bash +cd /path/to/sdk-internal +git log --oneline .. -- \ + crates/bitwarden-core crates/bitwarden-crypto crates/bitwarden-vault +``` + +For each commit, check for: + +- **Type renames** (e.g., `AsymmetricCryptoKey` -> `PrivateKey`) +- **New required struct fields** (e.g., new `Option` fields on `CipherView`) +- **Removed or deprecated functions** (look for `#[deprecated]` annotations) +- **Changed function signatures** (parameter types, return types) +- **Trait changes** (new required methods, changed generic bounds) + +To check the public API diff for a specific crate: + +```bash +git diff .. -- crates/bitwarden-crypto/src/keys/mod.rs +git diff .. -- crates/bitwarden-crypto/src/lib.rs +git diff .. -- crates/bitwarden-vault/src/cipher/cipher.rs +``` + +Cross-reference findings against `references/api-surface.md` to assess impact. + +### 6. Apply Code Changes + +1. **Cargo.toml** — Update all three `rev = "..."` to the new SHA +2. **Rust source files** — Fix compilation errors from breaking changes +3. **Deprecation warnings** — Add `#[allow(deprecated)]` with a comment explaining why +4. Do NOT make unrelated formatting or style changes + +### 7. Build and Test (Claude) + +```bash +cd util/RustSdk/rust + +# Compile +cargo build + +# Review transitive dependency changes (focus on crypto crates) +git diff Cargo.lock | grep "^[+-]name\|^[+-]version" | head -40 + +# Verify FFI signatures unchanged +git diff ../NativeMethods.g.cs + +# Run Rust tests (roundtrip test is critical) +cargo test + +# Run C# integration tests +dotnet test test/SeederApi.IntegrationTest/ + +# Format checks +cargo fmt --check +``` + +**Key validation:** The `encrypt_decrypt_roundtrip_preserves_plaintext` test proves the new SDK +version correctly encrypts and decrypts Vault Data. If this passes, the crypto is working. + +### 8. Human Verification (HUMAN ONLY — Claude does NOT run these) + +Present these commands to the human engineer. Wait for confirmation before proceeding. + +**SeederUtility — seed an org with vault data:** + +```bash +cd util/SeederUtility +dotnet run -- organization -n SdkBumpTest -d sdk-bump-test.example -u 3 -c 10 -g 5 -o Traditional -m +``` + +**SeederUtility — seed a fixture preset:** + +```bash +dotnet run -- seed --preset dunder-mifflin-enterprise-full --mangle +``` + +**SeederApi — start and seed via HTTP:** + +You need to replace the empty password argument with at least an 8-character master password for the fake user account + +```bash +cd util/SeederApi +dotnet run +# In another terminal: +curl -X POST http://localhost:5000/seed \ + -H "Content-Type: application/json" \ + -H "X-Play-Id: sdk-bump-test" \ + -d '{"template": "SingleUserScene", "arguments": {"email": "test@example.com", "password": ""}}' +``` + +**SeederApi — cleanup:** + +```bash +curl -X DELETE http://localhost:5000/seed/sdk-bump-test +``` + +**Validation criteria (human checks):** + +- Seeded users can log in to the web vault with the fake master password + - See `util/SeederUtility/README.md` for the default master password used by Seeder +- Vault Data (ciphers) is visible and decryptable in the web client +- No errors in SeederUtility or SeederApi output +- SeederApi cleanup deletes all tracked entities + +--- + +## Worked Example: February 2026 Bump + +This section documents the actual bump performed in Feb 2026 as a reference. + +### Context + +- **Old rev:** `7080159154a42b59028ccb9f5af62bf087e565f9` (2025-11-20) +- **Target:** `web-v2026.2.0` production release +- **NPM version:** `@bitwarden/sdk-internal` `0.2.0-main.522` +- **Workflow ID:** `126086102` (Publish @bitwarden/sdk-internal) +- **New rev:** `abba7fdab687753268b63248ec22639dff35d07c` (2026-02-05) + +### Breaking Changes Found + +| Change | Impact | Fix | +| --------------------------------------------------------- | ---------------------------------------- | ------------------------------------------ | +| `AsymmetricCryptoKey` renamed to `PrivateKey` | Import + usage in lib.rs | Rename type | +| `AsymmetricPublicCryptoKey` renamed to `PublicKey` | Import + usage in lib.rs | Rename type | +| `CipherView` added `attachment_decryption_failures` field | Test struct literal in cipher.rs | Add `attachment_decryption_failures: None` | +| `PrivateKey::to_der()` returns `Pkcs8PrivateKeyBytes` | Low risk — auto-refs to `KeyEncryptable` | No code change needed | +| `encapsulate_key_unsigned` deprecated | Deprecation warning | `#[allow(deprecated)]` + comment | + +### Cargo.lock Review + +- All bitwarden-\* crates: `1.0.0` -> `2.0.0` (workspace version bump — expected) +- `coset`: `0.3.8` -> `0.4.1` (COSE library — minor bump) +- New transitive deps: `mockall`, `predicates`, `tracing-attributes` (test/dev deps) +- **No changes to core crypto crates** (rsa, aes, sha2, hmac, pbkdf2) + +### Results + +- 7/7 Rust unit tests passed +- 65/65 C# integration tests passed +- NativeMethods.g.cs unchanged +- Human verification: login, vault decryption, seeding all confirmed working