Skip to content

Releases: sealandseacat/dbmask

v0.1.1 - the safety release

Choose a tag to compare

@sealandseacat sealandseacat released this 24 Aug 15:48
9e9ef68

[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 mask without --apply could write to the database while printing "DRY-RUN (no changes written)" when the config set masking.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-partial is passed explicitly; dbmask scan exits 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 both scan ("Needs review") and mask (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 --apply later writes. [#9]
  • llm.api_style is now actually configurable. LocalProvider supported 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_email now lands on the reserved, undeliverable example.invalid domain. The previous behavior (original domain preserved — often enough to identify a small organization) is available explicitly as fake_email_keep_domain.
  • Masked values stay valid for their data type. New default strategies: uuidfake_uuid (a real v4 UUID), ip_addressfake_ip (valid octets), credit_cardfake_credit_card (Luhn-valid), date/date_of_birthfake_date (deterministic ±30–730-day shift,
    always a real calendar date). format_random/shuffle now 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-values to display them. Keeps PII out of terminals, scrollback and CI logs.
  • mask warns when running with the publicly-known default seed, and scan/mask warn before sampled values are sent to an external LLM provider.

Added

  • dbmask validate --strict (and ValidationReport.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_sensitive merely because detection was inconclusive at the time. Delete dbmask_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 mask writing without --apply (via dry_run: false in YAML) must now pass --apply.

dbmask 0.1.0

Choose a tag to compare

@sealandseacat sealandseacat released this 21 Aug 00:20

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 → masked pair 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 locked on SQLite (#2)
  • fake_* strategies no longer crash on Python 3.9 (#3)

Install

pip install dbmask

Full details in CHANGELOG.md.