Personal note. I wrote this little helper during a larger network migration to bulk-edit comments and groups in an unencrypted Winbox 4.1
.cdbfile. Out of 100+ saved devices, about a dozen entries needed adjusting — doing that one by one through the Winbox GUI is exhausting, so a small CLI that runs one targeted edit per invocation made the batch tractable. It's a personal tool that turned out useful enough to share; the parser was reverse-engineered from a single sample file, so treat it accordingly.
A small Python CLI to inspect and edit a Winbox 4 connection database (*.cdb).
Targets one connection at a time, looked up by its address (IP or MAC).
Supports list, show, set (comment / group) and delete.
No third-party dependencies — uses only the Python 3 standard library.
python3 winbox_cdb.py <file.cdb> list [--group GROUP]
python3 winbox_cdb.py <file.cdb> show --addr <IP-or-MAC>
python3 winbox_cdb.py <file.cdb> set --addr <IP-or-MAC> [--new-addr <IP-or-MAC>] [--user TEXT] [--password TEXT] [--romon-agent <IP>] [--comment TEXT] [--group TEXT]
python3 winbox_cdb.py <file.cdb> delete --addr <IP-or-MAC> [-y]# Show all entries.
python3 winbox_cdb.py sample.cdb list
# Show only entries in a specific group.
python3 winbox_cdb.py sample.cdb list --group Rathaus
# Inspect every field of one entry.
python3 winbox_cdb.py sample.cdb show --addr 10.109.9.35
# Update its comment and group in one go.
python3 winbox_cdb.py sample.cdb set --addr 10.109.9.35 \
--comment "RAT-C-COR-A" --group "RAT-C (Core)"
# Change the address (renumbering / replacing hardware).
# Letters in MAC addresses are upper-cased automatically.
python3 winbox_cdb.py sample.cdb set --addr 10.109.9.35 --new-addr 10.109.9.36
python3 winbox_cdb.py sample.cdb set --addr 10.109.9.36 --new-addr aa:bb:cc:dd:ee:ff
# Rotate credentials.
python3 winbox_cdb.py sample.cdb set --addr 10.109.9.35 --user "operator" --password "newpw"
# Change or clear the RoMON agent (only on entries that already use one).
python3 winbox_cdb.py sample.cdb set --addr 00:15:5D:09:7B:39 --romon-agent 10.109.9.36
python3 winbox_cdb.py sample.cdb set --addr 00:15:5D:09:7B:39 --romon-agent ""
# Clear the comment.
python3 winbox_cdb.py sample.cdb set --addr 10.109.9.35 --comment ""
# Delete an entry (asks for confirmation; pass -y to skip it).
python3 winbox_cdb.py sample.cdb delete --addr 10.109.9.35--addr is matched against the entry's address field (fid 1):
- IPv4 addresses are matched exactly (
10.109.9.35). - MAC addresses are matched case-insensitively (
AA:BB:…==aa:bb:…). - If 0 entries match, the command fails with exit code
2. - If more than 1 entry matches, the command fails with exit code
3and lists them. Every operation targets exactly one entry — there is no bulk mode by design.
--new-addr (on set) accepts IPv4, IPv6 or a MAC address. Input is
validated and normalized before writing:
- MAC letters are upper-cased (
aa:bb:cc:dd:ee:ff→AA:BB:CC:DD:EE:FF). - IPv4 / IPv6 are stored in
ipaddresscanonical form. - Invalid values are rejected before any file is touched.
- If another entry already has the requested address,
setrefuses with exit code3and points at the colliding entry.
--romon-agent (on set) updates the RoMON Agent field (fid 11):
- Accepts an IPv4 or IPv6 address. Empty string (
"") clears the field. - Anything else is rejected before any file is touched.
- The tool only updates an existing RoMON Agent field; it cannot add the field to records that don't already have one. If a connection has no RoMON Agent yet, configure it once through Winbox first, then this tool can take over the future updates.
Every write command (set, delete):
-
Refuses to run if a
<file>.lockis present next to the database. -
Manages a single backup
<file>.baknext to the database, with the following session-aware policy:- If
<file>.bakdoes not exist → a fresh backup is created. - If
<file>.bakis ≤ 10 minutes old → it is reused, not overwritten. This protects the pre-batch state when several edits are run in quick succession (the first edit captures the safety net, subsequent edits keep that same safety net). - If
<file>.bakis > 10 minutes old → the command aborts and asks the user to rename or remove the stale backup first. This guards against silently overwriting a known-good backup with a file that may itself have been bricked by an earlier edit.
Each write reports whether the backup was
createdorreused. - If
-
Writes the new file atomically via
<file>.tmp+os.replace. -
Re-parses the freshly-written file and verifies that the targeted record really has the new value(s). On any verification failure the backup is restored and the command exits non-zero.
The parser is tested to round-trip the sample database byte-for-byte
(check_roundtrip.py sample.cdb), so an unedited write reproduces the
input exactly. An edit only changes:
- the bytes of the affected record body, and
- the 4-byte size prefix of that record.
All other records remain bit-identical.
file := MAGIC (4B = 0D F0 1D C0) record*
record := size:u32_le body[size]
body := "M2" kind_header field*
Two record kinds appear in the wild:
kind bytes (after M2) |
meaning |
|---|---|
05 00 00 00 |
top-level managed connection |
07 00 00 01 |
sub-entry under a category like IP Services |
Inside the body, every string field is one of:
explicit: <fid:u24_le> 21 <len:u8> <data>
implicit: 09 00 FE 21 <len:u8> <data> # used for fid 0 (name)
Field IDs known to this tool:
| fid | meaning |
|---|---|
| 0 | session name (implicit framing) |
| 1 | address (IP or MAC) — lookup key |
| 2 | login |
| 3 | password (sometimes encrypted) |
| 4 | comment / display label |
| 6 | <own> keep-password flag |
| 8 | group |
| 11 | RoMON agent (IPv4/IPv6, may be empty) |
| 12 | Session (e.g. IP Services) |
String length is one byte, so --comment / --group values are limited
to 254 bytes (UTF-8 encoded). The tool refuses longer values rather
than guessing a different encoding.
- No
addcommand. The per-record header contains small-int slots whose meaning isn't fully decoded; creating a fresh record from scratch risks Winbox rejecting or rewriting the file. Cloning an existing record and patching its fields is a safer path if this is ever needed. - Only fields with explicit framing can be safely set; fields 4
(comment) and 8 (group) both qualify. The implicit-framing name
field (fid 0) is not editable through
set. - The format was reverse-engineered from one sample file. If you
encounter a
.cdbthat fails the round-trip check, do not edit it with this tool — there's a wrinkle the parser hasn't seen.
winbox_cdb.py— the CLI tool (parser + editor in one file).check_roundtrip.py— sanity check: re-serialize and compare to original.sample.cdb— local working copy for development / testing.
MIT © 2026 dko-strd
The software is provided "as is", without warranty of any kind. See the
LICENSE file for the full text.