# Sync Pass Bw

> Sync pass (password-store) with Bitwarden — incremental sync with LLM-powered fuzzy matching, BW as source of truth

- **Type:** Skill
- **Install:** `agentstack add skill-pengguanya-claude-toolkit-sync-pass-bw`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [pengguanya](https://agentstack.voostack.com/s/pengguanya)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [pengguanya](https://github.com/pengguanya)
- **Source:** https://github.com/pengguanya/claude-toolkit/tree/main/skills/sync-pass-bw

## Install

```sh
agentstack add skill-pengguanya-claude-toolkit-sync-pass-bw
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Sync pass  Bitwarden

Incrementally synchronize [pass](https://www.passwordstore.org/) (the standard Unix password manager) with [Bitwarden](https://bitwarden.com/) 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](https://github.com/browserpass/browserpass-extension). This skill compares the two stores, reports differences, and can fix mismatches.

## Prerequisites

Before any operation, check that all tools are available:

```bash
# 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):
1. `$BW_SESSION` environment variable already set in the shell
2. `--session ` argument passed to this skill
3. 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

```bash
# 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:

1. **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.com`
   - `Work/Roche/pengg3` -> username=`pengg3`, domain=`Roche`
   - `Shopping/Amazon/amazon.com/user@email.com` -> username=`user@email.com`, domain=`amazon.com`, fallback=`Amazon`

2. **Find BW match**: Search BW items where:
   - `login.username` equals 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

3. **Compare passwords**: If a BW match is found, compare passwords directly:

```bash
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:

1. **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/bw_sync_items.json

   # Pass last modification (from git log)
   cd ~/.password-store && git log -1 --format="%ai" -- "$PASS_PATH.gpg"
   ```

2. **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?

3. **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"
   ```

4. 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/user` but 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/pengg3` won't match any URL -- suggest renaming to `Work/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:

```bash
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 status` before 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 interactive `pass insert` which 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`, and `pass rm` automatically 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](https://github.com/pengguanya)
- **Source:** [pengguanya/claude-toolkit](https://github.com/pengguanya/claude-toolkit)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-pengguanya-claude-toolkit-sync-pass-bw
- Seller: https://agentstack.voostack.com/s/pengguanya
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
