Releases: sealandseacat/dbmask
Releases · sealandseacat/dbmask
Release list
v0.1.1 - the safety release
[0.1.1] - 2026-08-24
Safety release: a top-to-bottom review of the failure modes that matter most in a masking tool — "it said dry-run but wrote", "it said masked but didn't", "it said verified but skipped". Upgrading before any real use is strongly recommended.
Security
dbmask maskwithout--applycould write to the database while printing "DRY-RUN (no changes written)" when the config setmasking.dry_run: false. The CLI flag is now the single source of truth. Tracked as GHSA-2jwm-hcfc-72xm.
Fixed
- Masking no longer proceeds on an incomplete scan. Columns whose analysis raised were silently left unmasked while the command exited 0. Masking now fails closed (
ScanIncompleteError/ exit 2) unless--allow-partialis passed explicitly;dbmask scanexits 3 when it could not analyze every column. [#6] - "Could not tell" is no longer recorded as "not sensitive". Inconclusive columns (no pattern match, LLM off — e.g. empty tables) were stored as safe with confidence 0.5 and reused from history forever. They are now
UNKNOWN: never persisted, never masked, and surfaced by bothscan("Needs review") andmask(explicit warning listing each one). [#7] - Sensitive primary-key columns are no longer silently skipped. The engine planned them, the preview showed them masked — but the UPDATE never touched them. They are now excluded up front and reported loudly (
TableMaskResult.skipped_columns, CLI "NOT MASKED — primary-key column"). [#8] - Dry runs no longer write to the seed map. Previews had a persistent side effect (recording original→masked pairs). Dry runs are now read-only; determinism keeps the preview identical to what
--applylater writes. [#9] llm.api_styleis now actually configurable.LocalProvidersupported OpenAI-style local servers (LM Studio, vLLM) but the config field did not exist and the factory never passed it. [#12]- Validation reports no longer leak sensitive values. FAIL details carry shape-redacted samples plus row keys instead of the original values. [#11]
Changed
- The masking-completeness check is now primary-key aligned. Rows are matched key-by-key and the sensitive column compared value-by-value, which catches a row whose email survived unmasked while its name changed — the case the old whole-row heuristic reported as fine. The heuristic remains only for keyless tables, and its clean result is now a WARNING ("cannot verify per-row without a primary key"), not a PASS. Tables present on only one side are also surfaced as warnings.
fake_emailnow lands on the reserved, undeliverableexample.invaliddomain. The previous behavior (original domain preserved — often enough to identify a small organization) is available explicitly asfake_email_keep_domain.- Masked values stay valid for their data type. New default strategies:
uuid→fake_uuid(a real v4 UUID),ip_address→fake_ip(valid octets),credit_card→fake_credit_card(Luhn-valid),date/date_of_birth→fake_date(deterministic ±30–730-day shift,
always a real calendar date).format_random/shufflenow preserve the Python type of ints, floats, Decimals, dates, datetimes, UUIDs and booleans instead of returning strings. [#10] - Mask previews redact original values by default (shape-only, e.g.
***-**); pass--show-valuesto display them. Keeps PII out of terminals, scrollback and CI logs. maskwarns when running with the publicly-known default seed, andscan/maskwarn before sampled values are sent to an external LLM provider.
Added
dbmask validate --strict(andValidationReport.passed_strict): warnings and skipped checks fail the gate; the non-strict summary line now names its caveats instead of printing a bare "PASSED".validation.pk_row_limit— row budget for the PK-aligned check, with partial coverage stated in the report.llm.send_values: false— metadata-only mode: the model judges from the column name alone; no data values leave the machine.--allow-partial(mask) and--show-values(mask) flags.- Strategies:
fake_uuid,fake_ip,fake_credit_card,fake_date,fake_email_keep_domain. - 57 new regression tests (121 total), most driving the real CLI against real config files and throwaway SQLite databases.
Upgrade notes
- History stores written by 0.1.0 may contain columns recorded as
not_sensitivemerely because detection was inconclusive at the time. Deletedbmask_history.db(or the affected rows) to have them re-evaluated as UNKNOWN. - Mappings for new values change for the email/uuid/ip/credit-card/date rules (they now produce valid-format output). Pairs already recorded in a seed map are preserved and keep winning; only values masked for the first time are affected.
- Scripts that relied on
dbmask maskwriting without--apply(viadry_run: falsein YAML) must now pass--apply.
dbmask 0.1.0
First public release.
dbmask discovers, masks, and validates sensitive data in any SQLAlchemy-supported database — for every environment production data flows to: dev/test, demos, analytics, vendor handoffs, AI pipelines.
Highlights
- Detection pipeline — manual overrides → value patterns → decision history → optional LLM (OpenAI or a fully local model), stopping at the first confident answer
- Masking engine — dictionary-backed fakes, format-preserving random, shuffle, redact, null/blank; deterministic and referentially consistent
- Seed map — durable
original → maskedpair tracking across tables, databases and runs (salted hashes; originals are never stored) - Validation — row counts, schema elements, and a row-based masking-completeness check; non-zero exit code for CI gates
- Any database — PostgreSQL, MySQL/MariaDB, SQL Server, Oracle, SQLite, and anything else with a SQLAlchemy dialect
Notable fixes landing in this release
- Masking tables larger than one batch no longer fails with
database is lockedon SQLite (#2) fake_*strategies no longer crash on Python 3.9 (#3)
Install
pip install dbmask
Full details in CHANGELOG.md.