Skip to content

Data Masking implementation plan

Finite boundary audit (#195)

Reviewed boundary Evidence Classification
Masker/config/provider/rule/selection/options/error/undefined exports Existing 240 exact masking cases, missing-rule and 64-invocation tests Verified finite API and ordered erase mapping; missing-rule strictness is a documented Go contract
Duplicate/overlapping selectors, captured plaintext and result ownership Four additional actual reference observations; TestDuplicateTransformSelectorsCaptureOriginalValues and existing provider barriers Duplicate transforms run independently against original values; completion order is not input order
Top-level undefined through transforms TestUndefinedTransformBoundaries Reproduced and fixed: retain the marker for empty selections and reject whole undefined before a string-only provider, instead of encrypting an empty object
Native clone errors before encryption TestStrictNativeCloneFailurePrecedesProviderCalls Strict JSON v2 rejects nonfinite/invalid Unicode/duplicate/cyclic/unsupported inputs before provider calls, preserving causes
Regex composition and replacement precedence Existing 30 masking/regex cases and shared regex corpus Verified finite replacement scope; Unicode sets remain #167
KMS keys/committed suite/authenticated context and invocation ownership Existing 39 TypeScript ciphertext cases, five failures and native concurrency/cancellation tests Real ESDK cryptography with local synthetic KMS wrapping; historical reverse interoperability retains its original identity
KMS factory/nil-client/cancellation and invalid-header errors TestClientSupplierFailuresReturnNoCiphertext, TestClientSupplierCancellationCrossesGeneratedBridge, TestInvalidMessageHeadersFailBeforeKMSRequests Reproduced and fixed: use the generated MPL opaque-error contract instead of panicking; preserve original factory errors. Invalid headers make no KMS requests. Linux execution is required

MASK-PARITY/MASK-KMS retain ancestor/descendant completion races, arbitrary structuredClone/prototype behavior, exhaustive algorithms/diagnostics, service acceptance and resource budgets. Caching stays #164; no ignored options are added.

Implementation history

Issue #116's bounded-cache planning is recorded in the KMS cache design. It compares the pinned upstream APIs and defines isolation, limits and acceptance; the provider remains uncached and implementation/interoperability gates stay open.

Reference: Powertools TypeScript v2.35.0, commit 7bcc27b1574493f9452688673658f52b80c53847. The installed package and lockfile provide the executable reference. Keep plain masking independent of optional encryption dependencies.

  • MASK-SOURCE: Audit DataMasking, public types/errors, field resolution, rule precedence, provider transforms and the KMS provider. Confirm dot/wildcard selection without JMESPath, structuredClone behavior, default mask, missing-field asymmetry, concurrent transforms and ignored providerOptions.
  • MASK-CORE: Implement an independent module with default/dynamic/custom erasure, ordered field rules, selectors, explicit errors, diagnostics and concurrent provider orchestration. Reuse Commons number parsing and object-key order. Verify 240 actual reference cases and native concurrency/ownership/cancellation/error/panic tests. This item does not include a built-in regex or AWS encryption provider.
  • MASK-LOCAL: Verify all 29 packaged modules and 26 standalone consumers with GOWORK=off, tests/vet/tidy and dependency isolation. The final Data Masking source passes a subsequent scoped package check and matches all eight archive files. Both CGO-disabled Linux builds passed; a runtime-only retry passed 847/847 RIE, 95/95 streaming and 14/14 Batch checks using the previously built binaries and corrected UTF-8 assertions. Docker executed amd64; arm64 was cross-compiled. Resources were cleaned and no AWS resources were used. Provider probes are not cryptographic interoperability.
  • MASK-REGEX-CORE: Verify the optional shared commons/regex module and Validation/Data Masking composition: 7,671 Node replacement cases, 19 invalid patterns, 30 actual TypeScript masking scenarios and all 23,071 existing Validation cases pass. Combined packaged checks cover 30 modules/27 independent consumers, followed by both CGO-disabled Linux builds, 856/856 RIE, 95/95 streaming and 14/14 Batch checks. See REGEX.md, MODULE_ACCEPTANCE_REGEX.json and LOCAL_VALIDATION.md. Full ECMAScript parity remains open.
  • MASK-REGEX: Finish Unicode sets (v), remaining advanced syntax/case-fold/property/backreference behavior, native serialization boundaries and exact diagnostic parity. See REGEX.md. Plain masking must remain independent of regex engine dependencies.
  • MASK-KMS-CORE: Implement and verify the optional uncached official AWS Encryption SDK provider: 39 TypeScript ciphertext cases, five malformed/tampered rejections, 108 Go-to-TypeScript assertions, multiple keys, authenticated context, 32 concurrent callers and cancellation/deadline/context preservation. Combined accepted checkpoints cover 31 modules/28 independent consumers; both CGO-disabled Linux builds, 868/868 RIE, 95/95 streaming and 14/14 Batch checks pass (2026-09-24). Docker executes amd64; arm64 is cross-compiled. Cache, full key/algorithm/error parity and actual KMS service acceptance remain open. See DATAMASKING_KMS.md and MODULE_ACCEPTANCE_KMS.json.
  • MASK-KMS: Finish the implemented optional AWS Encryption SDK provider's remaining key/algorithm/error compatibility boundaries under #195, preserving encrypted message format and context verification. Existing bidirectional interoperability is recorded under MASK-KMS-CORE; remaining real KMS acceptance belongs to the separately authorized service gate. Bounded caching remains its own feature in #164.
  • MASK-CACHE: Resolve GAP-04 in #164 with a supported equivalent of upstream data-key caching: capacity, age, message and byte thresholds, key/context partitioning and authenticated failures. A hierarchical keyring is not an automatic equivalent. Do not claim full parity from uncached encryption.
  • MASK-PARITY: Complete native/structuredClone/prototype/undefined/cycle/number/Unicode boundaries, array properties/holes, overlapping asynchronous transforms, all public type/error mappings and exact serialization/error text. Document intentional Go differences until resolved.
  • MASK-RELEASE: Establish cold/warm latency, allocation, payload and concurrency budgets; review dependencies/licenses and complete independent publication.

Source findings

Default erase collapses non-array data to five stars, masks each array element and preserves top-level null/undefined. A top-level rule without field/rule selectors visits leaves. Per-field rules run before ordinary fields and suppress repeated masking of the same concrete path. Field rules are ordered and can observe earlier replacements. A missing ordinary erase field throws by default or warns in ignore mode; missing maskingRules and encryption/decryption fields are silent. Nulls survive rules but a plain selected-field erase replaces them.

Encrypt without fields passes JSON.stringify(data) to the provider. Selected transforms capture source values before asynchronous writes. Decrypt treats a string as a whole encrypted payload regardless of fields, and skips selected non-string values with warnings. Provider options appear in declarations but are not forwarded by the pinned runtime. Encryption context is forwarded. Provider errors propagate directly; they are not all wrapped in DataMaskingEncryptionError.

The upstream AWS provider uses a KMS keyring and caching materials manager with defaults of 100 cache entries, 300 seconds, 4294967296 messages and Number.MAX_SAFE_INTEGER bytes. It returns Base64 ciphertext and checks that each requested decryption context entry matches the authenticated message header. The Go SDK documentation still states that data-key caching is unsupported; its hierarchical keyring is an alternative. This is a scoped unresolved dependency gap, not a reason to stop independent erasure/provider work.

Encryption provider implementation boundary

The initial implementation owns the optional datamasking/kms module and implements the existing Provider interface. Keep the AWS Encryption SDK and Materials Providers Library outside plain masking. Use the official SDK's authenticated message format, generator/additional keyrings and commitment policy. Verify requested context against authenticated decrypt output before returning plaintext.

Local acceptance can exercise actual Encryption SDK encryption/decryption using an injected KMS client/transport fixture, then exchange complete ciphertexts with the pinned TypeScript provider in both directions. Such a fixture establishes SDK message interoperability, not real KMS authorization or wrapping-key durability. Test wrong contexts, modified headers/frames, multiple keys, empty/non-ASCII plaintext and commitment policies. Do not substitute reversible mock framing for cryptographic interoperability.

AWS documents no Go data-key caching support. Its hierarchical keyring changes the key-management and interoperability contract, so it is not an equivalent of the upstream caching CMM. Resolve age, capacity, message/byte limits and key/context partitioning as their own acceptance gate; an uncached provider must state its limitation explicitly. Actual AWS tests require the user's cloud authorization.

Sources inspected on 2026-09-23: Go SDK support and installation, KMS keyring construction and requirements, official Go examples. Pin and inspect the chosen release's go.mod before implementation: the development module currently requires Go 1.24 although the guide states a 1.23 minimum; neither conflicts with this repository's Go 1.27 baseline.