[Column Masking] BLOCKER: BatchPointGet fast path bypasses masking policy
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Severity
BLOCKER
## Description
Any query that hits `BatchPointGetPlan` (typically `WHERE pk IN (...)` or unique key `IN`) returns original column values without masking. The current implementation only adds `MaskingExprs` to `PointGet`, but the `BatchPointGet` path has no corresponding mechanism.
## Impact
Column masking policy can be bypassed by simply rewriting SQL form (change `=` to `IN`), which is a security/compliance level feature failure.
## Example
```sql
-- Normal masking works
SELECT sensitive_col FROM t WHERE pk = 1; -- returns masked value
-- Bypasses masking
SELECT sensitive_col FROM t WHERE pk IN (1); -- returns original value!
```
## Related Code Locations
- `pkg/planner/core/point_get_plan.go:940` - fast plan prefers BatchPointGetPlan
- `pkg/planner/optimize.go:240` - optimize returns fast plan directly
- `pkg/planner/core/point_get_plan.go:442` - BatchPointGetPlan has no MaskingExprs field
- `pkg/executor/builder.go:5443` - building BatchPointGetExec without masking
- `pkg/executor/batch_point_get.go:196` - Next only decodes and returns rows
## Proposed Fix
**Option A (Recommended)**: Add `MaskingExprs` to `BatchPointGetPlan`/`BatchPointGetExec` similar to `PointGet`, and evaluate/replace output columns row-by-row before `Next` returns.
**Option B**: When target table/output columns have enabled masking policies, disable `tryWhereIn2BatchPointGet` (return nil), fallback to normal planner path.
## Parent Issue
Relates to #65744
Contributor guide
Assessment
This issue has not been assessed yet.