pingcap / pingcap/tidb

Masking policy: MASK_PARTIAL function implementation mismatch with design spec

Open
#67,225 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
40.5k
Forks
6.2k
PR merge metrics
PR metrics pending

Description

## Issue Description

The `MASK_PARTIAL` function implementation in the `demo` branch (commit 7b061d9918) does not match the design specification in `docs/design/2026-02-27-column-level-masking.md`.

## Current Implementation (demo branch)

The code in `pkg/expression/builtin_masking.go` implements:
- Function signature: `MASK_PARTIAL(col, mask_char, start, length)`
- `start` = zero-based position where masking begins
- `length` = number of characters to mask

Test usage: `MASK_PARTIAL('alpha', '*', 1, 2)` → `'a**ha'`
- Preserves position 0: `'a'`
- Masks positions 1-2: `'**'`
- Preserves positions 3-4: `'ha'`

## Design Specification

The design document specifies:
```
MASK_PARTIAL(col, preserve_left, preserve_right, mask_char)
```

Expected behavior for `MASK_PARTIAL('alpha', '*', 1, 2)`:
- preserve_left=1: Keep first 1 character `'a'`
- preserve_right=2: Keep last 2 characters `'ha'`
- Mask middle (5-1-2=2): `'**'`
- Result: `'a**ha'`

## Expected Behavior (per design spec)

| Function Call | Result | Explanation |
|--------------|--------|-------------|
| `MASK_PARTIAL('4532111111111111', '*', 4, 4)` | `'4532********1111'` | Show first 4, mask middle 8, show last 4 |
| `MASK_PARTIAL('1234567890', '*', 3, 4)` | `'123***7890'` | Show first 3, mask middle 3, show last 4 |
| `MASK_PARTIAL('123456789', '*', 3, 4)` | `'123**6789'` | Show first 3, mask middle 2, show last 4 |

## Correct Implementation

The `origin/release-8.5-20260312-v8.5.5` branch has the correct implementation that follows the design specification.

## Impact

The current `demo` branch implementation makes it difficult to use `MASK_PARTIAL` for common masking patterns:
- Credit card masking `MASK_PARTIAL(cc, '*', 4, 4)` requires knowing the exact card length and calculating `start`/`length`
- The design spec semantics `MASK_PARTIAL(cc, '*', 4, 4)` (show first 4, show last 4) is more intuitive

## Suggested Fix

1. Update `pkg/expression/builtin_masking.go` to implement the design spec semantics: `MASK_PARTIAL(col, mask_char, preserve_left, preserve_right)`
2. Update integration tests in `tests/integrationtest/t/privilege/column_masking_policy.test`
3. Update expected results in `tests/integrationtest/r/privilege/column_masking_policy.result`

## References

- Design doc: `docs/design/2026-02-27-column-level-masking.md` (line 166)
- Current implementation: `pkg/expression/builtin_masking.go` (line 333-395)
- System table constraint: `UNIQUE KEY uk_table_policy (table_id, policy_name)` (table-scoped policy names)

**Note**: There may also be a related issue with policy name uniqueness being checked at database scope instead of table scope in the `demo` branch. The system table constraint `uk_table_policy (table_id, policy_name)` indicates policy names should be unique per table, not per database.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.