Install
$ agentstack add skill-pengguanya-claude-toolkit-sync-pass-bw ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Sync pass Bitwarden
Incrementally synchronize pass (the standard Unix password manager) with Bitwarden using LLM-powered fuzzy matching.
Why this skill exists: pass-import does one-time bulk imports from Bitwarden CSV/JSON exports. It doesn't handle ongoing sync, detect password drift, or deal with the inevitable naming mismatches between the two stores. This skill does all three, using Claude's reasoning to match entries even when names differ (e.g., pass entry Github/github.com/user matches BW entry named "GitHub").
How it works: Bitwarden is the source of truth. The pass store is a deliberate subset — most users only put entries in pass that they need for CLI access, scripting, or browser autofill via browserpass. This skill compares the two stores, reports differences, and can fix mismatches.
Prerequisites
Before any operation, check that all tools are available:
# 1. Bitwarden CLI installed?
which bw || echo "ERROR: bw not found. Install: https://bitwarden.com/help/cli/"
# 2. Bitwarden auth status
bw status 2>&1
# 3. pass installed?
which pass || echo "ERROR: pass not found. Install: https://www.passwordstore.org/"
# 4. pass store exists?
ls ~/.password-store/*.gpg 2>/dev/null || ls ~/.password-store/**/*.gpg 2>/dev/null || echo "WARNING: password store appears empty"
# 5. jq installed? (required for JSON processing)
which jq || echo "ERROR: jq not found. Install: sudo apt install jq"
If Bitwarden is locked, tell the user: > Your Bitwarden vault is locked. Please run ! bw unlock in Claude Code, then provide the session key. You can also pass it directly: /sync-pass-bw sync --session
If Bitwarden is unauthenticated, tell the user: > Please run ! bw login first to authenticate with your Bitwarden account.
Session handling
The BW session key can come from (check in this order):
$BW_SESSIONenvironment variable already set in the shell--sessionargument passed to this skill- Ask the user to provide it
Use --session "$BW_SESSION" or export BW_SESSION=... in all bw commands.
Interpreting Arguments
$ARGUMENTS
| Input pattern | Action | |---|---| | Empty or "status" or "check" | Run Compare workflow only (read-only report) | | "sync" | Run Compare + Fix Mismatches workflows | | "full" | Run Compare + Fix Mismatches + Structural Improvements workflows | | --session (in any position) | Extract the session key and use it for bw commands |
Workflow: Compare
Read-only comparison that produces a status report. This always runs first.
Step 1: Fetch all data
# Get all BW items as JSON (save to temp file for repeated queries)
bw list items --session "$BW_SESSION" 2>/dev/null > /tmp/bw_sync_items.json
echo "BW entries: $(jq length /tmp/bw_sync_items.json)"
# Get all pass entry paths
find ~/.password-store -name '*.gpg' | sed 's|.*/.password-store/||; s|\.gpg$||' | sort > /tmp/pass_entries.txt
echo "Pass entries: $(wc -l /dev/null | sed 's|.*/.password-store/||; s|/$||'
# Show depth distribution
find ~/.password-store -name '*.gpg' | sed 's|.*/.password-store/||; s|\.gpg$||' | awk -F/ '{print NF}' | sort | uniq -c
Common patterns to detect:
- Flat:
domain.com/username.gpg(2 levels) - Categorized:
Category/domain.com/username.gpg(3 levels) - Nested:
Category/Service/domain.com/username.gpg(4 levels) - Mixed: combination of the above
The last path segment is always the username (or a label). The segment before it is typically the domain or service name. Earlier segments are organizational categories.
Step 3: Match and compare each pass entry
For each pass entry, find the corresponding Bitwarden entry using fuzzy matching:
- Extract from pass path:
- Username: the last path segment (filename without .gpg)
- Domain: the parent directory name
- Fallback domain: the grandparent directory name (for deeply nested structures)
Examples:
Social/twitter.com/myuser-> username=myuser, domain=twitter.comWork/Roche/pengg3-> username=pengg3, domain=RocheShopping/Amazon/amazon.com/user@email.com-> username=user@email.com, domain=amazon.com, fallback=Amazon
- Find BW match: Search BW items where:
login.usernameequals the extracted username, AND- Any URI contains the domain string, OR the BW entry name contains the domain string (case-insensitive)
- If no match: retry with the fallback domain (grandparent directory)
- If still no match: try matching username alone and present candidates for the user to confirm
- Compare passwords: If a BW match is found, compare passwords directly:
PASS_PW=$(pass show "$PASS_PATH" 2>/dev/null | head -1)
BW_PW=$(jq -r --arg u "$USERNAME" --arg d "$DOMAIN" '
[.[] | select(
.login.username == $u and
((.login.uris // [] | any(.uri | test($d; "i"))) or (.name | test($d; "i")))
)] | .[0].login.password // ""
' /tmp/bw_sync_items.json)
if [ -z "$BW_PW" ]; then
STATUS="PASS_ONLY"
elif [ "$PASS_PW" = "$BW_PW" ]; then
STATUS="SYNCED"
else
STATUS="PW_DIFFERS"
fi
Step 4: Report results
Present results grouped by status:
## Sync Status Report
### SYNCED (N entries)
| Pass Path | BW Entry |
|---|---|
### PW_DIFFERS (N entries) -- action needed
| Pass Path | BW Entry | Pass pw_len | BW pw_len | BW revised |
|---|---|---|---|---|
### PASS_ONLY (N entries) -- exists in pass but no BW match found
| Pass Path | Username | Domain | Notes |
|---|---|---|---|
### Summary
- Total pass entries: N
- Synced: N
- Password differs: N
- Pass-only (no BW match): N
If there are PASS_ONLY entries, use your judgment to determine if they might match a BW entry with a different naming convention. Present potential matches and ask the user to confirm.
Workflow: Fix Mismatches
Only run when user requests "sync" or "full". Requires the Compare workflow to have completed.
For each PW_DIFFERS entry:
- Determine which is newer:
```bash # BW revision date jq -r --arg u "$USERNAME" --arg d "$DOMAIN" ' [.[] | select(.login.username == $u and ((.login.uris // [] | any(.uri | test($d; "i"))) or (.name | test($d; "i"))) )] | .[0].revisionDate ' /tmp/bwsyncitems.json
# Pass last modification (from git log) cd ~/.password-store && git log -1 --format="%ai" -- "$PASS_PATH.gpg" ```
- Present the mismatch to the user:
> ``: BW password (revised YYYY-MM-DD) differs from pass (last changed YYYY-MM-DD). Password lengths: pass=N, BW=M. > Recommended: Update pass from BW (source of truth). > Update pass from BW?
- Apply fix (only after user confirms):
``bash BW_PW=$(jq -r --arg u "$USERNAME" --arg d "$DOMAIN" ' [.[] | select(.login.username == $u and ((.login.uris // [] | any(.uri | test($d; "i"))) or (.name | test($d; "i"))) )] | .[0].login.password ' /tmp/bw_sync_items.json) echo "$BW_PW" | pass insert -f -e "$PASS_PATH" ``
- After all fixes, re-run the comparison to confirm 0 mismatches remain.
Workflow: Structural Improvements
Only run when user requests "full". This workflow analyzes and suggests -- it never auto-applies changes.
Analysis 1: Naming mismatches
Look for pass entries where the path doesn't reflect the current BW entry name. This happens when BW entries are renamed but pass paths aren't updated.
- Example: pass has
ChatGPT/auth0.openai.com/userbut BW entry is named "OpenAI" - Suggestion:
pass mv "ChatGPT/auth0.openai.com/user" "OpenAI/auth0.openai.com/user"
Analysis 2: Redundant pass entries
Multiple pass entries that map to the same single BW entry (same password, same username, different paths).
- This can be intentional -- e.g., browserpass users keep separate entries per subdomain so autofill works on each URL
- Flag these but explain the trade-off: consolidating simplifies the store, but may break browser autofill for specific URLs
Analysis 3: BW duplicates
Multiple BW entries with the same username and overlapping URIs. These are likely accidental duplicates in BW itself.
- Suggest: merge in the Bitwarden web vault (pass can't fix this)
Analysis 4: Browserpass compatibility
If browserpass is detected (which browserpass-linux64 || which browserpass-linux32 || ls ~/.mozilla/native-messaging-hosts/com.github.browserpass.native.json 2>/dev/null):
- Check that pass entries contain the actual login domain in their path
- Flag entries where the domain segment doesn't match any BW URI (autofill won't work)
- Example:
Work/CompanySSO/pengg3won't match any URL -- suggest renaming toWork/sso.company.com/pengg3
Present all suggestions as a numbered list. Let the user approve, skip, or modify each one.
Cleanup
After all operations, remove temp files:
rm -f /tmp/bw_sync_items.json /tmp/pass_entries.txt
Important Rules
- NEVER display passwords in plain text in your output. Only use them inside pipe commands (
echo "$PW" | pass insert ...) or variable comparisons in bash. To show differences, display password lengths only. - NEVER create new pass entries without explicit user confirmation.
- NEVER delete pass entries without explicit user confirmation.
- NEVER modify Bitwarden entries -- this skill treats BW as read-only source of truth. Suggest BW changes for the user to make in the web vault.
- ALWAYS check
bw statusbefore any query -- if locked or unauthenticated, stop and guide the user. - ALWAYS use
pass insert -f -e(force + echo mode) when updating passwords programmatically. Never use interactivepass insertwhich requires typing the password twice. - ALWAYS confirm each password update individually with the user before executing.
- ALWAYS clean up temp files (
/tmp/bw_sync_items.json,/tmp/pass_entries.txt) when done. - The pass store is typically git-managed -- every
pass insert,pass mv, andpass rmautomatically creates a git commit. Do not run additional git commands in the pass store. - When matching fails, present candidates and ask -- do not guess silently.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: pengguanya
- Source: pengguanya/claude-toolkit
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.