masker-db 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. masker_db-0.1.0/.gitignore +50 -0
  2. masker_db-0.1.0/CHANGELOG.md +67 -0
  3. masker_db-0.1.0/CONTRIBUTING.md +189 -0
  4. masker_db-0.1.0/LICENSE +21 -0
  5. masker_db-0.1.0/PKG-INFO +483 -0
  6. masker_db-0.1.0/README.md +436 -0
  7. masker_db-0.1.0/SECURITY.md +78 -0
  8. masker_db-0.1.0/examples/demo.py +130 -0
  9. masker_db-0.1.0/examples/demo.sh +161 -0
  10. masker_db-0.1.0/examples/make_demo_db.py +255 -0
  11. masker_db-0.1.0/examples/masker.yaml +100 -0
  12. masker_db-0.1.0/examples/schema.sql +88 -0
  13. masker_db-0.1.0/pyproject.toml +164 -0
  14. masker_db-0.1.0/scripts/bench_sqlite.py +191 -0
  15. masker_db-0.1.0/src/masker/__init__.py +11 -0
  16. masker_db-0.1.0/src/masker/__main__.py +8 -0
  17. masker_db-0.1.0/src/masker/cli.py +695 -0
  18. masker_db-0.1.0/src/masker/config.py +705 -0
  19. masker_db-0.1.0/src/masker/connect.py +320 -0
  20. masker_db-0.1.0/src/masker/console.py +58 -0
  21. masker_db-0.1.0/src/masker/engine.py +679 -0
  22. masker_db-0.1.0/src/masker/errors.py +104 -0
  23. masker_db-0.1.0/src/masker/init.py +148 -0
  24. masker_db-0.1.0/src/masker/keys.py +188 -0
  25. masker_db-0.1.0/src/masker/py.typed +0 -0
  26. masker_db-0.1.0/src/masker/report.py +171 -0
  27. masker_db-0.1.0/src/masker/scan.py +528 -0
  28. masker_db-0.1.0/src/masker/schema.py +425 -0
  29. masker_db-0.1.0/src/masker/transformers/__init__.py +47 -0
  30. masker_db-0.1.0/src/masker/transformers/base.py +286 -0
  31. masker_db-0.1.0/src/masker/transformers/constant.py +33 -0
  32. masker_db-0.1.0/src/masker/transformers/date_shift.py +126 -0
  33. masker_db-0.1.0/src/masker/transformers/email.py +125 -0
  34. masker_db-0.1.0/src/masker/transformers/fake.py +81 -0
  35. masker_db-0.1.0/src/masker/transformers/hash.py +89 -0
  36. masker_db-0.1.0/src/masker/transformers/keep.py +35 -0
  37. masker_db-0.1.0/src/masker/transformers/null.py +34 -0
  38. masker_db-0.1.0/src/masker/transformers/number_noise.py +101 -0
  39. masker_db-0.1.0/src/masker/transformers/partial_mask.py +56 -0
  40. masker_db-0.1.0/src/masker/transformers/preserve_format.py +56 -0
  41. masker_db-0.1.0/src/masker/transformers/redact.py +33 -0
  42. masker_db-0.1.0/src/masker/transformers/regex_replace.py +65 -0
  43. masker_db-0.1.0/src/masker/transformers/registry.py +20 -0
  44. masker_db-0.1.0/src/masker/transformers/shuffle.py +105 -0
  45. masker_db-0.1.0/src/masker/values.py +215 -0
  46. masker_db-0.1.0/src/masker/verify.py +445 -0
  47. masker_db-0.1.0/tests/conftest.py +265 -0
  48. masker_db-0.1.0/tests/test_cli.py +827 -0
  49. masker_db-0.1.0/tests/test_config.py +364 -0
  50. masker_db-0.1.0/tests/test_connect.py +229 -0
  51. masker_db-0.1.0/tests/test_engine.py +577 -0
  52. masker_db-0.1.0/tests/test_integration_mysql.py +188 -0
  53. masker_db-0.1.0/tests/test_integration_postgres.py +204 -0
  54. masker_db-0.1.0/tests/test_integration_sqlite.py +340 -0
  55. masker_db-0.1.0/tests/test_keys.py +134 -0
  56. masker_db-0.1.0/tests/test_perf.py +132 -0
  57. masker_db-0.1.0/tests/test_regressions.py +183 -0
  58. masker_db-0.1.0/tests/test_report.py +178 -0
  59. masker_db-0.1.0/tests/test_scan.py +294 -0
  60. masker_db-0.1.0/tests/test_schema.py +183 -0
  61. masker_db-0.1.0/tests/test_transformers.py +675 -0
  62. masker_db-0.1.0/tests/test_values.py +210 -0
  63. masker_db-0.1.0/tests/test_verify.py +291 -0
@@ -0,0 +1,50 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ .eggs/
7
+ build/
8
+ dist/
9
+ wheels/
10
+ pip-wheel-metadata/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # Tooling caches
18
+ .cache/
19
+ .pytest_cache/
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ .coverage
23
+ .coverage.*
24
+ coverage.xml
25
+ htmlcov/
26
+ .tox/
27
+ .nox/
28
+
29
+ # Editors / OS
30
+ .idea/
31
+ .vscode/
32
+ *.swp
33
+ .DS_Store
34
+
35
+ # Secrets and local databases -- never commit these
36
+ *.key
37
+ *.pem
38
+ .env
39
+ .env.*
40
+ *.sqlite
41
+ *.sqlite3
42
+ *.db
43
+
44
+ # Masker run artifacts
45
+ .masker/
46
+
47
+ /build-prompt.txt
48
+ /after-build-report.txt
49
+ /PUBLISHING.md
50
+ /VERIFICATION.md
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - Unreleased
8
+
9
+ First public release.
10
+
11
+ ### Added
12
+
13
+ - **CLI** (`masker`) with the commands `init`, `scan`, `validate`, `run`,
14
+ `verify`, `report` and `transformers`, plus the global flags `--verbose`,
15
+ `--no-color`, `--key-file`, `--ephemeral-key` and `--version`.
16
+ - **Rules file** (`masker.yaml`) with per-table actions (`copy`, `skip`,
17
+ `truncate`, `subset`), per-column transformer rules, and a `settings` block
18
+ for batch size, NULL handling, schema creation, row caps, Faker locale,
19
+ uniqueness retries and sample sizes. Unknown keys are rejected rather than
20
+ ignored.
21
+ - **Thirteen transformers**: `fake`, `hash`, `partial_mask`, `redact`, `null`,
22
+ `constant`, `shuffle`, `regex_replace`, `preserve_format`, `date_shift`,
23
+ `number_noise`, `email` and `keep`, all registered by name and all
24
+ deterministic under a shared key.
25
+ - **Determinism** from `HMAC-SHA256(secret_key, column_scope ‖ value)`, with
26
+ per-column namespacing that is shared across both sides of a foreign key so
27
+ joins survive masking.
28
+ - **Key management**: key from `MASKER_SECRET_KEY` or `--key-file`, never from
29
+ the rules file; refusing to run without one; refusing keys shorter than 12
30
+ characters; warning below 32; `--ephemeral-key` for throwaway output.
31
+ - **Uniqueness handling** with counter-salted retries and a clear error when the
32
+ value space is too small.
33
+ - **Schema reflection** over SQLAlchemy Core for SQLite, PostgreSQL and
34
+ MySQL/MariaDB, including primary keys, unique constraints (including SQLite
35
+ inline `UNIQUE`, which is invisible to the usual inspector API), column
36
+ lengths and nullability.
37
+ - **Foreign-key-aware ordering** using union-find grouping, with cycle detection
38
+ and deferred constraint checks for tables that cannot be ordered.
39
+ - **`masker init`** generating a starter rules file covering every column, with
40
+ PII suggestions marked `# SUGGESTED, review me`.
41
+ - **`masker scan`** combining column-name patterns with value sniffing on a
42
+ sample of rows, listing columns with no rule, `--strict` as a CI gate, and
43
+ `--json` output.
44
+ - **`masker run`** with batched streaming (default 5000 rows), one transaction
45
+ per table, Rich progress, `--dry-run` with a partially hidden before/after
46
+ sample, `--batch-size`, `--tables`, `--exclude`, `--subset-rows`,
47
+ `--create-schema/--no-create-schema`, `--allow-non-empty` and `--yes`.
48
+ - **`masker verify`** sample-checking the target for surviving originals,
49
+ reporting columns with no rule that look like PII, counting orphaned foreign
50
+ keys, and distinguishing genuine leaks from the coincidences that
51
+ domain-preserving transformers (`date_shift`, `number_noise`) legitimately
52
+ produce.
53
+ - **`masker report`** rendering a run summary as Markdown or JSON, from the
54
+ stats file a run writes.
55
+ - **Safety guards**: read-only source connections on all three backends,
56
+ same-database refusal on normalized URLs, non-empty-target refusal, credential
57
+ redaction in every URL and message, no row values in logs, and a per-run list
58
+ of columns copied verbatim.
59
+ - **Test suite** covering every transformer, rules validation, PII heuristics,
60
+ FK ordering, the safety guards and leak detection, plus an end-to-end SQLite
61
+ integration test and opt-in PostgreSQL/MySQL integration tests.
62
+ - **Examples**: a sample schema, a sample `masker.yaml` and a 13-step SQLite
63
+ demo script (`examples/demo.sh`).
64
+ - **Benchmark**: `scripts/bench_sqlite.py`, the source of the numbers in the
65
+ README's Performance section.
66
+
67
+ [0.1.0]: https://github.com/zahidhasann88/masker/releases/tag/v0.1.0
@@ -0,0 +1,189 @@
1
+ # Contributing to Masker
2
+
3
+ Thanks for considering it. Masker is a privacy tool, so the bar for correctness
4
+ is higher than usual: a transformer that is subtly wrong leaks data, and a
5
+ safety check that is subtly wrong is worse than no check at all. Please read
6
+ the notes below before opening a pull request.
7
+
8
+ ## Getting set up
9
+
10
+ ```bash
11
+ git clone https://github.com/zahidhasann88/masker && cd masker
12
+ python -m venv .venv && source .venv/bin/activate
13
+ pip install -e '.[dev,postgres,mysql]'
14
+ pre-commit install
15
+ ```
16
+
17
+ `make check` runs everything CI runs:
18
+
19
+ ```bash
20
+ make check # ruff + mypy (strict) + pytest with coverage
21
+ make lint # ruff check
22
+ make format # ruff format
23
+ make typecheck # mypy --strict on src/
24
+ make test # pytest
25
+ make demo # end-to-end SQLite demo (examples/demo.sh)
26
+ make bench # 100k-row benchmark
27
+ ```
28
+
29
+ ## Running the tests
30
+
31
+ The default suite runs everywhere — it needs no services, because the
32
+ integration tests use SQLite:
33
+
34
+ ```bash
35
+ pytest
36
+ pytest --cov=masker --cov-report=term-missing
37
+ MASKER_RUN_PERF=1 pytest -m perf # opt-in performance test
38
+ ```
39
+
40
+ The PostgreSQL and MySQL suites are skipped unless you point them at a
41
+ disposable database. **They drop and recreate databases**, so never point them
42
+ at anything you care about.
43
+
44
+ ```bash
45
+ export MASKER_TEST_POSTGRES_URL="postgresql+psycopg://postgres@localhost:5432/masker_test"
46
+ export MASKER_TEST_MYSQL_URL="mysql+pymysql://root@127.0.0.1:3306/masker_test"
47
+ pytest -m "postgres or mysql"
48
+ ```
49
+
50
+ Both are exercised in CI against service containers.
51
+
52
+ ## Code style
53
+
54
+ - Python 3.10+ syntax, `from __future__ import annotations` everywhere.
55
+ - `src/` layout; the package is `masker`.
56
+ - ruff (line length 100) and mypy in `strict` mode on `src/` must pass. The
57
+ config lives in `pyproject.toml`; prefer fixing the code over adding an
58
+ ignore, and if an ignore is genuinely needed, say why in a comment.
59
+ - Tests go in `tests/`, one module per source module where that mapping makes
60
+ sense. Coverage floor is 85%.
61
+
62
+ ## Commit messages and pull requests
63
+
64
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/):
65
+
66
+ ```
67
+ feat: add a `tokenize` transformer for FPE-style masking
68
+ fix: stop `verify` flagging shifted dates as leaks
69
+ docs: document the cyclic foreign-key limitation
70
+ test: cover the unique-retry path for regex_replace
71
+ chore: bump ruff to 0.6
72
+ ```
73
+
74
+ Keep one logical change per commit. In the PR description, say what the change
75
+ does, why, and — for anything touching masking or the safety guards — how you
76
+ verified it.
77
+
78
+ ## How to add a transformer
79
+
80
+ A transformer is one module in `src/masker/transformers/`, registered by name.
81
+ The interface is deliberately small.
82
+
83
+ **1. Create the module.** For a transformer called `tokenize`:
84
+
85
+ ```python
86
+ # src/masker/transformers/tokenize.py
87
+ """``tokenize`` -- one-line description, plus a YAML example.
88
+
89
+ ```yaml
90
+ columns:
91
+ ssn: {transformer: tokenize, params: {prefix: "TKN"}}
92
+ ```
93
+
94
+ Explain what it guarantees and what it does not. This docstring is the
95
+ documentation, so write it for someone deciding whether to trust it with a
96
+ column of personal data.
97
+ """
98
+
99
+ from __future__ import annotations
100
+
101
+ from typing import Any
102
+
103
+ from masker.transformers.base import BaseParams, BaseTransformer
104
+ from masker.transformers.registry import register
105
+
106
+
107
+ class TokenizeParams(BaseParams):
108
+ """Validated by pydantic; add constraints here rather than in `_apply`."""
109
+
110
+ prefix: str = ""
111
+
112
+
113
+ @register
114
+ class TokenizeTransformer(BaseTransformer[TokenizeParams]):
115
+ name = "tokenize"
116
+ summary = "One-line summary shown by `masker transformers`."
117
+ params_model = TokenizeParams
118
+
119
+ # Declare the properties Masker needs to know about. Defaults are in
120
+ # BaseTransformer; only override what differs.
121
+ supports_uniqueness = True # can produce distinct outputs per input?
122
+ tracks_uniqueness = True # should Masker enforce a UNIQUE constraint?
123
+ value_scoped = True # does output depend on the input value?
124
+ requires_prepass = False # does it need the whole column up front?
125
+ preserves_domain = False # can output land on another row's original?
126
+
127
+ def _apply(self, value: Any, attempt: int) -> Any:
128
+ # `value` is never None here unless null_passthrough is disabled;
129
+ # use self.require_not_null(value) to get a clear error otherwise.
130
+ # `attempt` is 0 on the first try and increments on a uniqueness retry,
131
+ # so it must influence the output for retries to make progress.
132
+ # Derive randomness from self.ctx.rng(...) or self.ctx.digest(...) --
133
+ # never from the global random module, or determinism breaks.
134
+ token = self.ctx.hex_digest(value, attempt, length=8)
135
+ return f"{self.params.prefix}{token}"
136
+ ```
137
+
138
+ **2. Export it.** Add the module to `src/masker/transformers/__init__.py` so
139
+ the `@register` decorator runs at import time.
140
+
141
+ **3. Test it.** `tests/test_transformers.py` has the patterns. At minimum cover:
142
+
143
+ - determinism: the same key and value produce the same output, twice;
144
+ - a different key produces a different output;
145
+ - NULL handling, both with `null_passthrough` on and off;
146
+ - length and type fitting into a narrow column;
147
+ - uniqueness: two distinct inputs give distinct outputs, and the retry path
148
+ (`attempt > 0`) makes progress;
149
+ - anything specific to your guarantee (format preservation, bounds, locale).
150
+
151
+ **4. Document it.** Add a section to the transformer reference in `README.md`
152
+ with a YAML example, and a line to `CHANGELOG.md`.
153
+
154
+ **5. Consider `validate`.** If your transformer only makes sense for certain
155
+ column types — or can provably never satisfy a UNIQUE constraint — say so in
156
+ `masker/config.py` so the mistake is caught by `masker validate` instead of at
157
+ row 9,000,000.
158
+
159
+ ### Rules for transformers
160
+
161
+ These are not style preferences; breaking them breaks guarantees users rely on.
162
+
163
+ 1. **No unseeded randomness.** Everything random must come from
164
+ `self.ctx.rng(...)` or `self.ctx.digest(...)`, both derived from the key and
165
+ the value. Output must be reproducible.
166
+ 2. **Never log or raise with the original value.** Errors name the column, not
167
+ the data. `MaskerError` messages are printed to the console.
168
+ 3. **Output must fit the column.** Return something `fit_value` can coerce; if
169
+ your transformer produces strings, they will be truncated to the column
170
+ length, so prefer generating at the right length.
171
+ 4. **`attempt` must change the output** if the transformer claims
172
+ `supports_uniqueness`, otherwise a uniqueness retry loops forever.
173
+ 5. **Set `preserves_domain = True` only if it is true** — that is, if a masked
174
+ value can legitimately equal another row's original. It controls whether
175
+ `verify` reports a match as a warning or as a leak, so getting it wrong
176
+ either hides a leak or cries wolf.
177
+
178
+ ## Reporting issues
179
+
180
+ Use the issue templates. For anything that looks like a security problem, see
181
+ [SECURITY.md](SECURITY.md) instead — please do not open a public issue.
182
+
183
+ A useful bug report includes the Masker version (`masker --version`), the
184
+ Python version, the backend, a `masker.yaml` with the secrets removed, and the
185
+ redacted error output.
186
+
187
+ ## Code of conduct
188
+
189
+ This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zahid Hasan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.