Skip to content

feat(export): per-column anonymization in the export dialog - #606

Closed
iamthenuggetman wants to merge 2 commits into
TabularisDB:mainfrom
iamthenuggetman:feat/export-anonymization
Closed

feat(export): per-column anonymization in the export dialog#606
iamthenuggetman wants to merge 2 commits into
TabularisDB:mainfrom
iamthenuggetman:feat/export-anonymization

Conversation

@iamthenuggetman

Copy link
Copy Markdown
Contributor

Closes #483

What

Adds the optional anonymization step to the file export proposed in the issue, as a transform layer between the row stream and the CSV/JSON/Markdown sinks — so it works for every driver (built-in and plugin) with no driver changes.

Export menu → Anonymized export… opens a dialog where each column of the result set can be assigned a rule:

  • NULL / fixed value — replace with a real NULL or a literal (***)
  • Partial mask — keep first/last N chars, mask the middle. Email-shaped values keep their shape: john@example.comj***@***.com
  • HMAC pseudonym (pseudonymization) — HMAC-SHA256 with a per-export key: the same input always produces the same output, so joins on pseudonymized keys (e.g. user_id) still work across tables exported with the same key. The key is shown in the dialog — reuse it across exports for stable pseudonyms, or regenerate it for a fresh set.

NULL values always pass through untouched — anonymizing them would invent data the source never had.

Per the issue's wording note, the feature is presented as column anonymization / pseudonymization, with an explicit hint in the dialog that it is not a "GDPR compliance" guarantee.

Implementation

  • src-tauri/src/export/anonymize/ — rule types, RowAnonymizer (transform applied to streamed rows before the sinks), unit tests
  • export.rsexport_query_to_file accepts an optional anonymize spec; stream_to_sink applies it. hmac added as a direct dep (already in the tree transitively)
  • ExportAnonymizeModal.tsx — the rule-assignment dialog (follows the modal styling rules; Escape handling, per-export key field with regenerate)
  • Editor.tsx — new "Anonymized export…" item in the export dropdown; the spec flows through handleExportCommon
  • i18n strings for all 11 locales

Tests

  • 14 Rust unit tests: fixed/NULL replacement, null passthrough, unlisted columns, HMAC determinism + key/input sensitivity, non-string value handling, partial-mask edges (short strings, multibyte, email shapes), serde payload match with the frontend
  • 2 frontend tests for the key generator
  • Full suites green: 3530 vitest, 46 cargo export tests, tsc -b / eslint / clippy clean
  • Manually tested against the demo databases: CSV export with partial-masked email, fixed-value name, and HMAC user_id; re-export with the same key produces identical pseudonyms, regenerating the key produces fresh ones

Out of scope (possible follow-ups from the issue)

Synthetic data (seeded faker), generalization (birth date → year, zip → region), and name-based heuristics to pre-suggest rules — the rule engine and dialog are structured so these slot in as additional rule types later.

Tim McKeage added 2 commits August 1, 2026 12:04
Adds an optional anonymization step to the file export, as a transform
layer between the row stream and the CSV/JSON/Markdown sinks — so it
works for every driver (built-in and plugin) with no driver changes.

Export menu → "Anonymized export…" opens a dialog where each column
can be assigned a rule:

- NULL / fixed value — replace with a real NULL or a literal (***)
- Partial mask — keep first/last N chars; emails keep their shape
  (john@example.com → j***@***.com)
- HMAC pseudonym — deterministic per export key, so joins on
  pseudonymized columns still work across tables exported with the
  same key; the key is shown and can be reused or regenerated

NULLs always pass through untouched. Presented as anonymization/
pseudonymization, not as a GDPR-compliance guarantee.

Closes TabularisDB#483
<input
type="number"
min={0}
max={9}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: max={9} on the number input is not enforced by the onChange handler. A user can type "99" and it will be accepted, bypassing the UI constraint. Add a cap in the onChange handler (e.g. Math.min(9, Math.max(0, parseInt(e.target.value) || 0))) to match the declared maximum.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
src/components/modals/ExportAnonymizeModal.tsx 203 max={9} on number inputs is not enforced in the onChange handler — a user can type values > 9 which bypasses the UI constraint
Files Reviewed (17 files)
  • src-tauri/src/export.rs - 0 issues
  • src-tauri/src/export/anonymize/mod.rs - 0 issues
  • src-tauri/src/export/anonymize/tests.rs - 0 issues
  • src/components/modals/ExportAnonymizeModal.tsx - 1 issue
  • src/i18n/locales/de.json - 0 issues
  • src/i18n/locales/en.json - 0 issues
  • src/i18n/locales/es.json - 0 issues
  • src/i18n/locales/fr.json - 0 issues
  • src/i18n/locales/it.json - 0 issues
  • src/i18n/locales/ja.json - 0 issues
  • src/i18n/locales/ko.json - 0 issues
  • src/i18n/locales/pt-BR.json - 0 issues
  • src/i18n/locales/ru.json - 0 issues
  • src/i18n/locales/tl.json - 0 issues
  • src/i18n/locales/zh.json - 0 issues
  • src/pages/Editor.tsx - 0 issues
  • src/utils/anonymize.ts - 0 issues
  • tests/utils/anonymize.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by ling-3.0-flash-free · Input: 180.8K · Output: 32.4K · Cached: 1.4M

@iamthenuggetman

Copy link
Copy Markdown
Contributor Author

Closing for now — holding this one back for a later round. Branch stays intact; will reopen when ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: Export data with column anonymization

1 participant