Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
#KP2BW_UPDATE=1
#KP2BW_INCLUDE_OVERSIZE_SECRETS=0

# URL handling.
# Match mode for plain URLs migrated into login URIs:
# default (default, leaves match unset = Bitwarden account default) | domain (force base-domain,
# reproduces KeePassXC) | host | startswith | exact | regex | never.
#KP2BW_URI_MATCH=default
# Interpret KeePassXC URL syntax on additional URLs (quoted = exact, * = wildcard); 0 imports them as plain strings.
#KP2BW_INTERPRET_URI_SYNTAX=1

# Behavior / output.
#KP2BW_YES=0
#KP2BW_VERBOSE=0
Expand All @@ -47,6 +55,15 @@
# values above 3600 are clamped.
#KP2BW_HTTP_TIMEOUT=180

# Upgrade existing items: re-fold legacy KP2A_URL*/AndroidApp custom fields into login URIs,
# then exit (no migration; no KeePass database needed). For imports made before URL folding.
#KP2BW_MIGRATE_URIS=0

# Print a read-only URI collision report and exit (changes nothing): groups login URLs by
# registrable domain, lists the ones with multiple hosts (the entries that all autofill together
# under base-domain match). 'keepass' reads the db, 'bitwarden' reads the live vault.
#KP2BW_REPORT_URIS=bitwarden

# Finalize mode.
# When set, kp2bw removes the KP2BW_ID dedup stamp from every migrated item and exits
# (no migration; no KeePass database needed). Run once the migration is complete and
Expand Down
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,48 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Fixed

- **`bw serve` is no longer orphaned on teardown (POSIX), which previously hung the process.** On Linux/macOS `bw` is
commonly a node launcher that spawns a worker; teardown signalled only the tracked PID, leaving the worker alive -- it
kept the port and, when kp2bw's stdout was a pipe, held the pipe open so the parent pipeline never reached EOF (a
multi-minute "still running" hang) and accumulated orphaned `bw serve` processes across runs. `bw serve` is now
started in its own session (`start_new_session=True`) and torn down by signalling the whole process group (SIGTERM,
then SIGKILL after a timeout), so the launcher and worker die together. Windows teardown (taskkill /T + port reap) is
unchanged.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- **An empty environment variable no longer shadows the matching `.env` entry.** `KP2BW_KEEPASS_FILE=""` (or any
empty-string export) used to override the `.env` value -- `load_dotenv(override=False)` treats an empty export as
"set" -- producing a baffling "KeePass database path is required" even when `.env` clearly had it. `_load_dotenv` now
fills any variable that is unset *or empty* from the file, while a real *non-empty* shell variable still wins (the
documented CLI flag > env var > default precedence is preserved for meaningful values).

### Added

- **`--report-uris keepass|bitwarden` -- a read-only URI collision report** (env `KP2BW_REPORT_URIS`). Groups every
login URL by registrable domain (a curated two-level public-suffix heuristic, so `10bis.co.il` stays whole) and lists
the domains with more than one host -- exactly the logins that all surface together under Bitwarden's base-domain
matching. `keepass` reads the database (previewing post-migration collisions); `bitwarden` reads the live vault
(honouring `-o`/`-c`). It changes nothing -- it just prints, so you can decide which entries to switch to Host match
(or flip your account's default URI match detection).

- **Additional URLs and Android packages migrate as Bitwarden login URIs, not custom fields** -- a KeePass(XC) entry's
additional URLs (`KP2A_URL`/`KP2A_URL_n`, plus the plainer `URL`/`URL_n` convention) and Android packages
(`AndroidApp`/`AndroidApp_n`, including the no-underscore `AndroidApp1` variant) were copied verbatim into custom
fields, where they were inert. They now become real entries in `login.uris`, so one login autofills across every site
and app it covered in KeePass; free-text URL labels (`API Url`, `Alt. URL`, `Website`, …) are deliberately left as
custom fields. Each URI gets a per-URI match mode reproducing KeePassXC's behaviour: a plain URL → the account default
(`match` left unset -- what Bitwarden itself writes on export; `--uri-match` / `KP2BW_URI_MATCH` overrides, e.g.
`domain` forces base-domain to replicate KeePassXC's host-based matching), a double-quoted URL → exact, and a `*`
wildcard → starts-with (trailing path) or regex. `AndroidApp` becomes an `androidapp://` URI. Non-web schemes
(`keepassxc://`, `cmd://`, `kdbx://`, `file://`) and unresolved `{REF:…}` URLs are dropped rather than left as dead
URIs. `--no-interpret-uri-syntax` (`KP2BW_INTERPRET_URI_SYNTAX`) disables the quote/wildcard interpretation for a
literal import. Bitwarden applies a regex to the whole URL (unlike KeePassXC's separate host/path regexes), so complex
wildcards are emitted as a best-effort whole-URL regex with a warning to review. Items imported before this are
upgraded by a normal re-run (the change is detected and the item updated in place); for users who don't want to
re-import, `kp2bw --migrate-uris` (env `KP2BW_MIGRATE_URIS`) is a Bitwarden-only one-shot pass that re-folds the
legacy fields into URIs on every existing item. Both honour `--uri-match` / `--interpret-uri-syntax` and `-o`/`-c`
scope.
- **Configurable per-request HTTP timeout via `KP2BW_HTTP_TIMEOUT`** -- the timeout for a single `bw serve` request is
now overridable through the `KP2BW_HTTP_TIMEOUT` environment variable (seconds), so a slow self-hosted server (e.g.
Vaultwarden) where an individual item write outlasts the default no longer times out. Non-numeric or non-positive
Expand Down
73 changes: 50 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,35 +85,62 @@ kp2bw [-h] [-V] [-k PASSWORD] [-K FILE] [-b PASSWORD] [-o ID]
[--path-to-name-skip N] [--skip-expired | --no-skip-expired]
[--include-recycle-bin | --no-include-recycle-bin]
[--metadata | --no-metadata] [--update | --no-update]
[--include-oversize-secrets] [--strip-ids] [-y] [-v] [-d]
[--include-oversize-secrets] [--uri-match MODE]
[--interpret-uri-syntax | --no-interpret-uri-syntax]
[--migrate-uris] [--report-uris SOURCE] [--strip-ids] [-y] [-v] [-d]
[FILE]
```

| Flag | Description | Env var |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `keepass_file` | Path to your KeePass 2.x database | `KP2BW_KEEPASS_FILE` |
| `-k, --keepass-password` | KeePass password (prompted if omitted) | `KP2BW_KEEPASS_PASSWORD` |
| `-K, --keepass-keyfile` | KeePass key file | `KP2BW_KEEPASS_KEYFILE` |
| `-b, --bitwarden-password` | Bitwarden password (prompted if omitted) | `KP2BW_BITWARDEN_PASSWORD` |
| `-o, --bitwarden-org` | Bitwarden Organization ID | `KP2BW_BITWARDEN_ORG` |
| `-c, --bitwarden-collection` | Collection ID, or `auto` to derive from top-level folder names | `KP2BW_BITWARDEN_COLLECTION` |
| `-t, --import-tags` | Only import entries with these tags | `KP2BW_IMPORT_TAGS` (comma-separated) |
| `--path-to-name` / `--no-path-to-name` | Prepend folder path to entry names (default: off) | `KP2BW_PATH_TO_NAME` |
| `--path-to-name-skip` | Skip first N folders in path prefix (default: 1) | `KP2BW_PATH_TO_NAME_SKIP` |
| `--skip-expired` | Skip entries that have expired in KeePass | `KP2BW_SKIP_EXPIRED` |
| `--include-recycle-bin` | Include Recycle Bin entries (excluded by default) | `KP2BW_INCLUDE_RECYCLE_BIN` |
| `--metadata` / `--no-metadata` | Toggle KeePass tags/expiry as a `KP2BW_META` field (default: on) | `KP2BW_MIGRATE_METADATA` |
| `--update` / `--no-update` | Update existing entries changed in KeePass (default: on) | `KP2BW_UPDATE` |
| `--include-oversize-secrets` | Offload over-limit secret fields[^offload] to a `.txt` attachment instead of dropping them (default: off) | `KP2BW_INCLUDE_OVERSIZE_SECRETS` |
| `--strip-ids` | Finalize: remove the `KP2BW_ID` dedup stamp from migrated items, then exit (no migration; no KeePass db) | `KP2BW_STRIP_IDS` |
| `-y, --yes` | Skip the Bitwarden CLI setup confirmation prompt | `KP2BW_YES` |
| `-v, --verbose` | Verbose output | `KP2BW_VERBOSE` |
| `-d, --debug` | Debug output — includes third-party library logs | `KP2BW_DEBUG` |
| `-V, --version` | Print the installed `kp2bw` version and exit | - |
| Flag | Description | Env var |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `keepass_file` | Path to your KeePass 2.x database | `KP2BW_KEEPASS_FILE` |
| `-k, --keepass-password` | KeePass password (prompted if omitted) | `KP2BW_KEEPASS_PASSWORD` |
| `-K, --keepass-keyfile` | KeePass key file | `KP2BW_KEEPASS_KEYFILE` |
| `-b, --bitwarden-password` | Bitwarden password (prompted if omitted) | `KP2BW_BITWARDEN_PASSWORD` |
| `-o, --bitwarden-org` | Bitwarden Organization ID | `KP2BW_BITWARDEN_ORG` |
| `-c, --bitwarden-collection` | Collection ID, or `auto` to derive from top-level folder names | `KP2BW_BITWARDEN_COLLECTION` |
| `-t, --import-tags` | Only import entries with these tags | `KP2BW_IMPORT_TAGS` (comma-separated) |
| `--path-to-name` / `--no-path-to-name` | Prepend folder path to entry names (default: off) | `KP2BW_PATH_TO_NAME` |
| `--path-to-name-skip` | Skip first N folders in path prefix (default: 1) | `KP2BW_PATH_TO_NAME_SKIP` |
| `--skip-expired` | Skip entries that have expired in KeePass | `KP2BW_SKIP_EXPIRED` |
| `--include-recycle-bin` | Include Recycle Bin entries (excluded by default) | `KP2BW_INCLUDE_RECYCLE_BIN` |
| `--metadata` / `--no-metadata` | Toggle KeePass tags/expiry as a `KP2BW_META` field (default: on) | `KP2BW_MIGRATE_METADATA` |
| `--update` / `--no-update` | Update existing entries changed in KeePass (default: on) | `KP2BW_UPDATE` |
| `--include-oversize-secrets` | Offload over-limit secret fields[^offload] to a `.txt` attachment instead of dropping them (default: off) | `KP2BW_INCLUDE_OVERSIZE_SECRETS` |
| `--uri-match MODE` | Match mode for plain URLs: `default`(default, account default)/`domain`/`host`/`startswith`/`exact`/`regex`/`never` | `KP2BW_URI_MATCH` |
| `--interpret-uri-syntax` | Honor KeePassXC quote/wildcard URL syntax on additional URLs (default: on; `--no-…` for literal) | `KP2BW_INTERPRET_URI_SYNTAX` |
| `--migrate-uris` | Upgrade existing items: re-fold legacy `KP2A_URL*`/`AndroidApp` fields into login URIs, then exit (no KeePass) | `KP2BW_MIGRATE_URIS` |
| `--report-uris SOURCE` | Print a read-only URI collision report (`keepass` or `bitwarden`) and exit; lists registrable domains with multiple hosts | `KP2BW_REPORT_URIS` |
| `--strip-ids` | Finalize: remove the `KP2BW_ID` dedup stamp from migrated items, then exit (no migration; no KeePass db) | `KP2BW_STRIP_IDS` |
| `-y, --yes` | Skip the Bitwarden CLI setup confirmation prompt | `KP2BW_YES` |
| `-v, --verbose` | Verbose output | `KP2BW_VERBOSE` |
| `-d, --debug` | Debug output — includes third-party library logs | `KP2BW_DEBUG` |
| `-V, --version` | Print the installed `kp2bw` version and exit | - |

Configuration precedence is always: CLI flag > environment variable > built-in default.

### Finalizing (`--strip-ids`)
### URL handling

A KeePass(XC) entry's additional URLs (`KP2A_URL`/`KP2A_URL_n`, plus the plainer `URL`/`URL_n` convention) and Android
packages (`AndroidApp`/`AndroidApp_n`, incl. the no-underscore `AndroidApp1` variant) are migrated as real Bitwarden
**login URIs** — not inert custom fields — so one login autofills across every site and app it covered in KeePass.
Free-text URL labels (`API Url`, `Alt. URL`, `Website`, …) are left as custom fields, since folding those would wrongly
autofill API endpoints and other metadata. Each URI gets a per-URI match mode reproducing KeePassXC's behaviour: a plain
URL → **account default** (`match` unset, what Bitwarden itself writes; pass `--uri-match domain` to force base-domain
and replicate KeePassXC's host-based matching), a double-quoted URL → **exact**, and a `*` wildcard → **starts-with**
(trailing path) or **regex**. Non-web schemes (`keepassxc://`, `cmd://`, `kdbx://`, `file://`) and unresolved `{REF:…}`
URLs are dropped. `--no-interpret-uri-syntax` disables the quote/wildcard interpretation and imports every URL as a
plain string.

Already imported before this existed? Two ways to upgrade: re-run a normal migration (the change is detected and the
items are updated in place), or — if you don't want to re-import — run `kp2bw --migrate-uris`, a Bitwarden-only one-shot
pass that re-folds the legacy fields into URIs on every existing item. Both honour
`--uri-match`/`--interpret-uri-syntax` and `-o`/`-c`.

Too many subdomains autofilling together? Under base-domain matching, every login under `*.example.com` surfaces on any
`example.com` subdomain. `kp2bw --report-uris keepass` (or `bitwarden`) prints a read-only collision report —
registrable domains with multiple hosts — so you can see which entries pile up and switch those to **Host** match (or
flip your Bitwarden account's default URI match detection to Host). It changes nothing; it just lists.

Every migrated item carries a plain-text `KP2BW_ID` custom field — the KeePass UUID kp2bw uses to match entries on
re-runs so nothing duplicates. Once you're satisfied the migration is complete and you're ready to fully adopt
Expand Down
Loading
Loading