diff --git a/.dumplingconf.example b/.dumplingconf.example index 9e20860..777aa4d 100644 --- a/.dumplingconf.example +++ b/.dumplingconf.example @@ -44,6 +44,10 @@ ssn = { strategy = "hash", salt = "${DUMPLING_USERS_SSN_SALT}", as_str # password/token — hash without revealing original password_hash = { strategy = "hash", as_string = true } api_token = { strategy = "redact", as_string = true } +# listed under [sensitive_columns] — explicit coverage for --strict-coverage / lint-policy +employee_number = { strategy = "hash", as_string = true } +tax_id = { strategy = "hash", salt = "${DUMPLING_USERS_SSN_SALT}", as_string = true } +national_id = { strategy = "hash", salt = "${DUMPLING_USERS_SSN_SALT}", as_string = true } # numeric fields age = { strategy = "int_range", min = 18, max = 90 } # random alphanumeric string of given length @@ -52,6 +56,8 @@ ref_code = { strategy = "string", length = 12 } external_id = { strategy = "uuid" } # set to SQL NULL legacy_field = { strategy = "null" } +# empty string clear (NOT NULL text); NULL source stays NULL +internal_notes = { strategy = "blank" } # temporal fuzzing date_of_birth = { strategy = "date_fuzz", min_days = -365, max_days = 365 } created_at = { strategy = "datetime_fuzz", min_seconds = -86400, max_seconds = 86400 } @@ -63,8 +69,13 @@ wake_time = { strategy = "time_fuzz", min_seconds = -3600, max_seconds = 360 credit_card = { strategy = "payment_card", length = 16, domain = "order_pan" } # monetary / numeric — random decimal in range with fixed fractional digits order_total = { strategy = "decimal", min = 0, max = 99999, scale = 2, domain = "order_amount" } -# keep the same anonymized email as users table via shared domain -customer_email = { strategy = "faker", faker = "internet::SafeEmail", domain = "customer_identity" } +# same anonymized email as users via shared domain (must use the same strategy) +customer_email = { strategy = "email", domain = "customer_identity" } +# listed under [sensitive_columns] +bank_account = { strategy = "string", length = 16 } +# JSON path rules — clear nested containers without wiping the whole document +"metadata.tags" = { strategy = "empty_array" } +"metadata.extras" = { strategy = "empty_object" } [rules."public.audit_log"] # unqualified table name also works (matches any schema) @@ -109,26 +120,39 @@ delete = [ ] # Supported operators: -# eq / neq — string equality (case_insensitive = true for case-insensitive) -# in / not_in — list membership (use `values = [...]`) -# like / ilike — SQL LIKE pattern (% and _); ilike is always case-insensitive -# regex / iregex — Rust regex; iregex is case-insensitive -# lt / lte / gt / gte — numeric compare (values parsed as f64) -# is_null / not_null — NULL check (no value needed) +# eq / neq — string equality (case_insensitive = true for case-insensitive) +# in / not_in — list membership (use `values = [...]`) +# like / ilike — SQL LIKE pattern (% and _); ilike is always case-insensitive +# not_like / not_ilike — negation of like / ilike (prefer over regex look-around) +# regex / iregex — Rust regex crate (not PCRE); look-around / backrefs rejected at load +# not_regex / not_iregex — negation of regex / iregex +# lt / lte / gt / gte — numeric compare (values parsed as f64) +# is_null / not_null — NULL check (no value needed) # --------------------------------------------------------------------------- # [[column_cases]] — conditional per-column strategy overrides # # For each row and column, Dumpling evaluates cases top-to-bottom and applies -# the FIRST matching case. Falls back to [rules] if none match. +# the FIRST matching case. Falls back to [rules] if none match. If no case +# matches and there is no [rules] default, the cell is left unchanged +# (keep-by-omission). # # when.any = OR semantics; when.all = AND semantics; both empty = always match. +# strategy = { strategy = "keep" } is only valid under column_cases (not [rules]). # --------------------------------------------------------------------------- [[column_cases."public.users".email]] # Admin emails → always redact (regardless of other conditions) when.any = [{ column = "is_admin", op = "eq", value = "true" }] strategy = { strategy = "redact", as_string = true } +[[column_cases."public.users".email]] +# Staff domains → keep original (default [rules] still scrubs everyone else) +when.any = [ + { column = "email", op = "ilike", value = "%@myco.com" }, + { column = "email", op = "ilike", value = "%@partner.example" }, +] +strategy = { strategy = "keep" } + [[column_cases."public.users".email]] # EU users → hash with a region-specific salt for GDPR compliance when.any = [{ column = "country", op = "in", values = ["DE", "FR", "GB", "IT", "ES"] }] diff --git a/AGENTS.md b/AGENTS.md index cb37b42..8cee123 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ src/ transform.rs — All anonymization strategies, PRNG, deterministic domain mapping sql.rs — SQL stream processor: INSERT + COPY parsing, column strategy selection, CREATE TABLE length extraction, sensitive coverage tracking - filter.rs — Row-filter predicate evaluation (eq/neq/like/regex/JSON-path/…) + filter.rs — Row-filter predicate evaluation (eq/neq/like/not_like/regex/…/JSON-path) scan.rs — Post-transform residual PII scanner (email/SSN/PAN/token regex) report.rs — JSON report / audit sidecar: provenance, streaming checksums, Reporter helper compressed_input.rs — gzip/ZIP wrappers; streaming vs temp materialization @@ -232,13 +232,19 @@ Follow these steps in order. Do not skip any step. ## How to Add a New Row Filter Predicate Operator -1. **`src/settings.rs` — `Predicate.op` doc comment**: Update the doc comment listing supported operator names. +1. **`src/settings.rs` — `KNOWN_PREDICATE_OPS`**: Add the operator name string to the const slice (used by validation). -2. **`src/filter.rs` — `predicate_matches`**: Add a match arm for the new operator string in the appropriate branch (`single-value` or `multi-value`). +2. **`src/settings.rs` — `Predicate.op` doc comment**: Update the doc comment listing supported operator names (and any regex / value-shape notes). -3. **Tests**: Add `#[test]` functions in `src/filter.rs`. +3. **`src/settings.rs` — `validate_predicate`**: Extend validation if the operator needs special value requirements or compile-time checks (see the `regex` / `not_regex` arms). -4. **`README.md`**: Add a row to the predicate operators table. +4. **`src/filter.rs` — `predicate_matches`**: Add a match arm for the new operator string in the appropriate branch (`single-value` or `multi-value`). + +5. **`src/lint.rs`**: If the operator can be statically invalid (like regex), extend lint checks so `dumpling lint-policy` surfaces the same class of errors. + +6. **Tests**: Add `#[test]` functions in `src/filter.rs` (and `settings.rs` / `lint.rs` when validation is involved). + +7. **`README.md`**: Add a row to the predicate operators table; mirror the same operators in `docs/src/configuration.md` and `.dumplingconf.example` comments when the list changes. --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 29f67ef..c3d7dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,19 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht - **JSON `--report` audit sidecar**: Dumpling version, per-run id and timestamp, config path + SHA-256, streaming input/output SHA-256, dump-seal digest cross-link (`seal_sha256`), explicit gate flags, and coverage/scan outcomes. `run_id` / `started_at` are instance metadata; policy fingerprint fields stay deterministic across re-runs ([#13](https://github.com/ababic/dumpling/issues/13)). - **`--no-seal`**: skip writing the dump-seal SQL comment (stdin/stdout pipelines). Incoming seals are still recognized; `--report` still records `seal_sha256`. +- **`keep` strategy** (under `[column_cases]` only): leave matching cells unchanged while a default `[rules]` entry scrubs the rest (for example staff-email allowlists). Counts toward `--strict-coverage`; kept cells are not counted as changed ([#77](https://github.com/ababic/dumpling/issues/77) / [#78](https://github.com/ababic/dumpling/pull/78)). +- **Negating predicate operators** `not_like`, `not_ilike`, `not_regex`, and `not_iregex` for row filters and `column_cases` `when` clauses ([#79](https://github.com/ababic/dumpling/issues/79) / [#81](https://github.com/ababic/dumpling/pull/81)). +- **`lint-policy` check `invalid-regex-predicate`**: flags unsupported or uncompilable `regex` / `iregex` / `not_regex` / `not_iregex` patterns ([#81](https://github.com/ababic/dumpling/pull/81)). + +### Changed + +- **Fail closed on invalid regex predicates**: unsupported Rust `regex` features (look-around, backreferences, possessive quantifiers) and patterns that fail to compile are rejected at config load instead of silently matching nothing ([#79](https://github.com/ababic/dumpling/issues/79) / [#81](https://github.com/ababic/dumpling/pull/81)). ### Docs - `--help` Examples plus long help for `--report` / `--no-seal`. mdBook and README cover dump seals, the JSON sidecar fields, and how to verify `seal_sha256` with or without a seal line. +- **Design rationale** page in mdBook (strengths, alternatives, and trade-offs) ([#84](https://github.com/ababic/dumpling/pull/84)). +- Keep-by-omission semantics for cases-only columns, `keep` cookbooks, and `not_*` predicate guidance aligned across README, configuration guide, CI guardrails, and `.dumplingconf.example` ([#82](https://github.com/ababic/dumpling/pull/82), [#81](https://github.com/ababic/dumpling/pull/81)). ## [0.7.0] - 2026-07-03 @@ -24,6 +33,7 @@ Stable **0.7.0** release (supersedes **0.7.0-alpha** and **0.7.0-beta** prerelea - **`scaffold-config` subcommand**: generate a draft starter `.dumplingconf` from a dump using heuristics and optional row sampling ([#67](https://github.com/ababic/dumpling/pull/67)). - **`decimal` and `payment_card` anonymization strategies** with per-strategy option documentation ([#62](https://github.com/ababic/dumpling/pull/62)). +- **`blank`, `empty_array`, and `empty_object` strategies**: empty-string clear for NOT NULL text; typed JSON `[]` / `{}` clears for path rules (NULL sources stay NULL; `domain` rejected) ([#62](https://github.com/ababic/dumpling/pull/62)). - **Gzip and ZIP inputs**: plain-SQL payloads inside **gzip** are decompressed **in-process** (streamed) when possible—no temporary file. Dumpling still materializes to the temp directory when required: **ZIP** archives (random-access central directory), **gzip wrapping `PGDMP`** or an inner **ZIP** (nested wrappers), or other cases where a filesystem path is needed for `pg_restore`. Temporary files are registered for removal when processing finishes. **`--in-place` is rejected** only when Dumpling had to write a **temporary** decompressed/extracted file (not when gzip plain-SQL streaming was used). Full multi-file ZIP packages (for example BACPAC) are still not supported as SQL input ([#70](https://github.com/ababic/dumpling/pull/70)). - **MIT License** ([#71](https://github.com/ababic/dumpling/pull/71)). diff --git a/README.md b/README.md index 8fe5e0d..4348372 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Follow these steps once; you will have a working path from “raw dump” to “ 2. **Or start from the example policy** — Copy [`.dumplingconf.example`](.dumplingconf.example) to `.dumplingconf` (or merge under `[tool.dumpling]` in `pyproject.toml`) and edit `[rules]` by hand. Set environment variables for `salt` and any `${…}` references so Dumpling can resolve secrets at startup. 3. **Align rules with your dump** — If you did not use `scaffold-config`, open the dump beside the config: `CREATE TABLE`, `COPY … (…)`, and `INSERT INTO … (…)` lines list identifiers for `[rules."table"]` or `[rules."schema.table"]` (see [Configuration (TOML)](#configuration-toml)). Trim rules to the tables you care about first, then extend columns and strategies as you go. 4. **Run Dumpling** — `dumpling -i dump.sql -o sanitized.sql` (add `-c path` if the config is not in the default search path). Use `dumpling --check -i dump.sql` when you only want to know whether anything would change. Pass **`--no-seal`** to skip the dump-seal comment on streaming output, and **`--report file.json`** for an audit sidecar. -5. **Tighten the policy** — Run `dumpling lint-policy` on your config. When you are ready for stricter gates, add `[sensitive_columns]` and use `--strict-coverage` / `--report` / `--scan-output` as described under [Usage](#usage). +5. **Tighten the policy** — Run `dumpling lint-policy` on your config (unsalted hashes, inconsistent domains, uncovered sensitive columns, invalid regex predicates). When you are ready for stricter gates, add `[sensitive_columns]` and use `--strict-coverage` / `--report` / `--scan-output` as described under [Usage](#usage). For allowlists inside a scrubbed column, use **`keep`** under `column_cases` (see [Conditional per-column cases](#conditional-per-column-cases)). The same flow is spelled out in the docs: [Getting started](https://ababic.github.io/dumpling/getting-started.html). @@ -600,6 +600,7 @@ dumpling lint-policy --config .dumplingconf # explicit config path | `unsalted-hash` | warning | `hash` strategy used without any salt — reversible for low-entropy inputs | | `inconsistent-domain-strategy` | error | Same domain name used with different strategies — breaks referential integrity | | `uncovered-sensitive-column` | error | A column in `[sensitive_columns]` has no matching rule or case | +| `invalid-regex-predicate` | error | A `regex` / `iregex` / `not_regex` / `not_iregex` pattern is missing, unsupported (look-around, backreferences, …), or fails to compile | Exits `0` if no violations are found, `1` if any violations exist. Plug it into CI as a pre-merge gate: @@ -624,4 +625,4 @@ See the [CI guardrails documentation](docs/src/ci-guardrails.md) for full pipeli ## Full documentation -Detailed docs, including the configuration reference and release process, are available at the project's [GitHub Pages site](https://ababic.github.io/dumpling/) (built from `docs/src/`). +Detailed docs — [Getting started](https://ababic.github.io/dumpling/getting-started.html), [Design rationale](https://ababic.github.io/dumpling/design-rationale.html), [Configuration guide](https://ababic.github.io/dumpling/configuration.html), [CI guardrails](https://ababic.github.io/dumpling/ci-guardrails.html), and the release process — are on the project's [GitHub Pages site](https://ababic.github.io/dumpling/) (built from `docs/src/`). diff --git a/docs/src/ci-guardrails.md b/docs/src/ci-guardrails.md index 3ed95e0..6409ae9 100644 --- a/docs/src/ci-guardrails.md +++ b/docs/src/ci-guardrails.md @@ -31,6 +31,7 @@ violations to stderr, and exits: | `unsalted-hash` | warning | A `hash` strategy is used with no salt (neither per-column `salt` nor global `salt`). Unsalted hashes are reversible via precomputed lookup tables for low-entropy inputs (names, emails, common IDs). | | `inconsistent-domain-strategy` | error | The same domain name is used with two or more different strategies. This breaks referential integrity: a domain shared between incompatible generators (for example `faker` with different `faker` targets, or `faker` vs `hash`) cannot maintain a single stable mapping. | | `uncovered-sensitive-column` | error | A column listed in `[sensitive_columns]` has no matching anonymization rule or case. The column will pass through unmodified, making the sensitive declaration misleading. | +| `invalid-regex-predicate` | error | A `regex` / `iregex` / `not_regex` / `not_iregex` pattern in `row_filters` or `column_cases` is missing, uses unsupported Rust `regex` features (look-around, backreferences, possessive quantifiers), or fails to compile. Prefer `not_like` / `not_ilike` / `not_regex` / `not_iregex`, or a default scrub plus `keep`, instead of negative lookahead. | --- @@ -204,3 +205,6 @@ Archive `report.json` with the sanitized SQL. To confirm a later re-run used the - In hardened security profile environments, a global `salt` is required anyway (`--security-profile hardened` will error without it), so `unsalted-hash` warnings become informational. +- Prefer `keep` under `column_cases` (or `not_ilike` / `not_iregex`) for + allowlist-style exceptions; do not rely on regex look-around — it is rejected + at config load and by `invalid-regex-predicate`. diff --git a/docs/src/configuration.md b/docs/src/configuration.md index 1e423ee..c02841e 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -199,11 +199,19 @@ Dumpling only exposes a **subset** wired in `src/faker_dispatch.rs`; unsupported ## Anonymization strategies -Strategy names and **per-strategy options** (`min`, `scale`, `as_string`, `faker`, …) are documented in the repository **README** under **Anonymization strategies** (each strategy lists only the keys it accepts, plus **Choosing a strategy** for when to prefer cheap vs realistic transforms, and **Cross-cutting options** for `domain`, `unique_within_domain`, and `as_string`). Row filters, JSON path rules, and conditional `column_cases` are also covered in the README before the full TOML example. +Strategy names and **per-strategy options** (`min`, `scale`, `as_string`, `faker`, …) are documented in the repository **README** under **Anonymization strategies** (each strategy lists only the keys it accepts, plus **Choosing a strategy** for when to prefer cheap vs realistic transforms, and **Cross-cutting options** for `domain`, `unique_within_domain`, and `as_string`). -**`column_cases` keep-by-omission:** if a column has only `[[column_cases.…]]` entries and **no** matching case (and no default `[rules]` entry / JSON path rules), the cell is left unchanged. That pattern is **supported** for selective anonymization (scrub matching rows; keep the rest). For the inverse shape (default scrub + allowlist exceptions), use the explicit `keep` strategy under `column_cases`. See the README section *Conditional per-column cases* for selection semantics and both cookbook examples. +Highlights that sit alongside the older clears and fakes: -The sections below expand on **JSON path rules** (same semantics as the README) and **secret references** in more depth. +- **`blank` / `empty_array` / `empty_object`** — cheap clears that preserve SQL `NULL` when the source is NULL (`blank` → `''`; the empty JSON strategies emit unquoted `[]` / `{}`). +- **`keep`** — leave the cell unchanged; **only** under `[column_cases]` (a default `[rules]` `keep` is rejected). Pair with a scrubbing default for allowlists. +- **`decimal` / `payment_card`** — bounded numeric shapes and Luhn-valid synthetic PANs. + +### Conditional `column_cases` + +For each cell, Dumpling evaluates `column_cases` in declaration order (first matching `when` wins), then falls back to a `[rules]` default when present. If nothing matches and there is no default / JSON path rule, the cell is **left unchanged** (keep-by-omission). That pattern is **supported** for selective anonymization (scrub matching rows; keep the rest). For the inverse shape (default scrub + allowlist exceptions), use the explicit `keep` strategy under `column_cases`. See the README section *Conditional per-column cases* for selection semantics and both cookbook examples. + +The sections below expand on **row-filter predicates**, **JSON path rules**, and **secret references**. ## Baseline config template @@ -213,6 +221,7 @@ salt = "${DUMPLING_GLOBAL_SALT}" [rules."public.users"] email = { strategy = "hash", salt = "${env:DUMPLING_USERS_EMAIL_SALT}", as_string = true } full_name = { strategy = "faker", faker = "name::Name" } +notes = { strategy = "blank" } [sensitive_columns] "public.users" = ["employee_number", "tax_id"] @@ -235,6 +244,7 @@ email = "medium" ssn = "high" pan = "critical" token = "high" + [row_filters."public.users"] retain = [ { column = "country", op = "eq", value = "US" }, @@ -244,6 +254,13 @@ delete = [ { column = "is_admin", op = "eq", value = "true" }, { column = "devices__platform", op = "eq", value = "android" } ] + +# Default scrub + keep exceptions (staff allowlist) +[[column_cases."public.users".email]] +when.any = [ + { column = "email", op = "ilike", value = "%@myco.com" }, +] +strategy = { strategy = "keep" } ``` ## Secret references @@ -350,15 +367,31 @@ template { salt = "${file:/run/secrets/dumpling_hmac_key}" ``` +## Row filters and predicates + +`[row_filters."table"]` can **`retain`** (OR: keep only if at least one predicate matches) and **`delete`** (drop if any predicate matches, evaluated after `retain`). The same predicate operators appear in `column_cases` `when.any` / `when.all`. + +| Operator | Description | +|---|---| +| `eq` / `neq` | String compare (case-insensitive if `case_insensitive = true`) | +| `in` / `not_in` | List of values (string compare) | +| `like` / `ilike` | SQL-like patterns (`%` and `_`) | +| `not_like` / `not_ilike` | Negation of `like` / `ilike` (prefer these over negative lookahead) | +| `regex` / `iregex` | [Rust `regex`](https://docs.rs/regex/) crate (`iregex` is case-insensitive). **Not PCRE** — look-around, backreferences, and possessive quantifiers are unsupported and **rejected at config load** (fail closed). | +| `not_regex` / `not_iregex` | Negation of `regex` / `iregex` | +| `lt` / `lte` / `gt` / `gte` | Numeric compare (values parsed as numbers) | +| `is_null` / `not_null` | No value needed | + +Invalid or unsupported regex patterns fail config load and surface as `invalid-regex-predicate` in `dumpling lint-policy`. For “scrub unless allowlisted domain” policies, prefer a default scrub plus a `keep` case, or positive `not_ilike` / `not_iregex` cases — see the README *Conditional per-column cases* and *Row filtering* sections. + Nested JSON targeting is supported in predicate `column` values via either: - dot notation (`payload.profile.tier`) - Django-style separators (`payload__profile__tier`) -When a JSON path traverses an array, Dumpling checks each element (useful for -list-of-dicts JSON structures). +When a JSON path traverses an array, Dumpling checks each element (useful for list-of-dicts JSON structures). -### JSON path rules (`json` / `jsonb` columns) +## JSON path rules (`json` / `jsonb` columns) You can anonymise values **inside** a text column that holds JSON using the same path syntax as row-filter predicates, but on **`[rules]` keys**: @@ -367,6 +400,8 @@ You can anonymise values **inside** a text column that holds JSON using the same The part before the first dot or `__` is the **SQL column name**; the rest is the path inside the parsed JSON document. Use **quoted** keys in TOML when the name contains dots. For a given table, you can use **either** path-level rules for a column **or** one whole-column rule for that column’s base name, not both (Dumpling rejects the conflict at startup). If a path is missing in a given row, that rule is skipped for that row. When only path rules apply (no whole-column rule), the rest of the JSON is left unchanged. Path rules are applied in **longest-path-first** order. `column_cases` still match the SQL column name only; use `when` predicates with nested `column` paths to branch on JSON content. +On JSON path leaves that must stay typed empty containers, use **`empty_array`** / **`empty_object`** instead of `null` or `blank`. + ## Safety recommendations - Prefer deterministic runs in CI by passing `--seed` (or `DUMPLING_SEED`). diff --git a/docs/src/design-rationale.md b/docs/src/design-rationale.md index a013e8a..acb9718 100644 --- a/docs/src/design-rationale.md +++ b/docs/src/design-rationale.md @@ -172,7 +172,7 @@ You sanitize **artifacts**, not production sockets. ### Rich, allowlisted strategies -From cheap clears (`null`, `redact`, `blank`, empty JSON containers) to realistic fakes (`email`, `name`, `payment_card`, `faker`, date/time fuzz). Config only carries **string identifiers**; new generators ship in Dumpling releases (`faker_dispatch`), never as eval’d user code. +From cheap clears (`null`, `redact`, `blank`, empty JSON containers) to realistic fakes (`email`, `name`, `payment_card`, `faker`, date/time fuzz), plus conditional **`keep`** under `column_cases` when some rows must retain the original value. Config only carries **string identifiers**; new generators ship in Dumpling releases (`faker_dispatch`), never as eval’d user code. ### Referential integrity via domains @@ -181,7 +181,8 @@ Optional `domain` + in-memory mapping cache (and optional uniqueness retries) ke ### Row filters and conditional cases - `row_filters` retain/delete whole rows before transforms. -- `column_cases` apply first-match-wins strategies per row. +- `column_cases` apply first-match-wins strategies per row (including `keep` for allowlist exceptions, or cases-only scrub with keep-by-omission). +- Predicate operators include positive and negating forms (`not_like` / `not_ilike` / `not_regex` / `not_iregex`); invalid Rust `regex` patterns fail closed at config load. - JSON path rules (dot or Django-style `__`) reach into `json` / `jsonb` text, including list-of-object shapes. ### Schema-aware string lengths @@ -195,7 +196,7 @@ Optional `domain` + in-memory mapping cache (and optional uniqueness retries) ke - **`--report`** JSON audit sidecar (hashes, flags, coverage, scan outcomes). - **`--strict-coverage`** against `[sensitive_columns]`. - **Residual PII scan** (`email` / SSN / PAN / token patterns) with fail thresholds. -- **`lint-policy`** for unsalted hashes, inconsistent domains, uncovered sensitive columns, and empty rule tables. +- **`lint-policy`** for unsalted hashes, inconsistent domains, uncovered sensitive columns, empty rule tables, and invalid regex predicates. - **`--security-profile hardened`**: OS CSPRNG + HMAC constructions when adversarial risk is in scope. ### Contributor-friendly engineering diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index f12911f..c8bd6f0 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -48,7 +48,7 @@ dumpling --help 4. **Run Dumpling** — `dumpling -i dump.sql -o sanitized.sql` (add `-c path` if the config is not in the default search path). Use `dumpling --check -i dump.sql` when you only want to know whether anything would change. Output is prefixed with a dump-seal comment by default; pass **`--no-seal`** for stdin/stdout pipelines, and **`--report file.json`** for an audit sidecar (see [Dump seal](configuration.md#dump-seal-on-by-default) and [JSON report](configuration.md#json-report-audit-sidecar)). -5. **Tighten the policy** — Run `dumpling lint-policy` on your config. When you are ready for stricter gates, add `[sensitive_columns]` and use `--strict-coverage`, `--report`, and `--scan-output` as described in the [configuration guide](configuration.md) and the repository `README.md`. +5. **Tighten the policy** — Run `dumpling lint-policy` on your config (catches unsalted hashes, inconsistent domains, uncovered `[sensitive_columns]`, and invalid regex predicates). When you are ready for stricter gates, add `[sensitive_columns]` and use `--strict-coverage`, `--report`, and `--scan-output` as described in the [configuration guide](configuration.md) and the repository `README.md`. For selective passthrough, see **`keep`** under `column_cases` and the `not_*` predicate operators in the README. ## PostgreSQL custom-format archives diff --git a/docs/src/index.md b/docs/src/index.md index e57b326..f94a224 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,7 +8,7 @@ This documentation covers the operating model for day-to-day use: - why Dumpling is designed the way it is ([Design rationale](design-rationale.md)), including alternatives and trade-offs, - how to build and run Dumpling locally, -- how to configure transformation behavior safely, +- how to configure transformation behavior safely (including `keep`, `column_cases`, and predicate operators), - how dump seals, `--no-seal`, and `--report` provide audit evidence, - how CI validates quality before changes merge, - and how maintainers produce tagged releases.