mirror of
https://github.com/bitwarden/server
synced 2026-02-28 10:23:24 +00:00
Craft tooling to aid in bumping RustSdk and Seeder document updating (#7077)
This commit is contained in:
41
.claude/commands/bump-rust-sdk.md
Normal file
41
.claude/commands/bump-rust-sdk.md
Normal file
@@ -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
|
||||
64
.claude/hooks/README.md
Normal file
64
.claude/hooks/README.md
Normal file
@@ -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
|
||||
42
.claude/hooks/rust-sdk-surface-check.sh
Executable file
42
.claude/hooks/rust-sdk-surface-check.sh
Executable file
@@ -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 }'
|
||||
61
.claude/hooks/seeder-docs-check.sh
Executable file
61
.claude/hooks/seeder-docs-check.sh
Executable file
@@ -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 }'
|
||||
141
.claude/skills/bump-rust-sdk/SKILL.md
Normal file
141
.claude/skills/bump-rust-sdk/SKILL.md
Normal file
@@ -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 <old-rev>..<new-rev> -- 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) |
|
||||
116
.claude/skills/bump-rust-sdk/references/api-surface.md
Normal file
116
.claude/skills/bump-rust-sdk/references/api-surface.md
Normal file
@@ -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::<KeyIds>::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::<KeyIds>` |
|
||||
|
||||
## 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<T>`)
|
||||
- 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 <old>..<new> -- crates/bitwarden-crypto/src/lib.rs crates/bitwarden-crypto/src/keys/mod.rs
|
||||
|
||||
# bitwarden-vault Cipher/CipherView changes
|
||||
git diff <old>..<new> -- crates/bitwarden-vault/src/cipher/cipher.rs crates/bitwarden-vault/src/cipher/login.rs
|
||||
|
||||
# bitwarden-core KeyIds
|
||||
git diff <old>..<new> -- crates/bitwarden-core/src/key_management/mod.rs
|
||||
```
|
||||
206
.claude/skills/bump-rust-sdk/references/methodology.md
Normal file
206
.claude/skills/bump-rust-sdk/references/methodology.md
Normal file
@@ -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 <tag>: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 <current-rev>
|
||||
```
|
||||
|
||||
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 <old-rev>..<new-rev> -- \
|
||||
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<T>` 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 <old-rev>..<new-rev> -- crates/bitwarden-crypto/src/keys/mod.rs
|
||||
git diff <old-rev>..<new-rev> -- crates/bitwarden-crypto/src/lib.rs
|
||||
git diff <old-rev>..<new-rev> -- 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
|
||||
Reference in New Issue
Block a user