聚合管道中禁用$substr脱敏,因其依赖固定长度、字节级截取致乱码、不可索引且驱动兼容性差;应改用应用层统一掩码和聚合阶段字段排除或视图隔离。

别在聚合管道里用 $substr 做脱敏,它不是掩码工具,也不安全。 真正能落地的脱敏必须在应用层统一控制,聚合阶段只适合做字段排除或视图隔离。
为什么 $substr + $concat 不是脱敏
常见写法如 {"$concat": ["***-**-", {"$substr": ["$id_number", -4, 4]}]} 看似能隐藏身份证号,但实际问题严重:
- 依赖固定长度:一旦
$id_number是空值、含空格、或长度不一致(比如港澳台证件),$substr就会截错甚至返回空 - 字节级截取:MongoDB 的
$substr在 3.4+ 实际调用的是$substrBytes,对中文、emoji 等 UTF-8 多字节字符会按字节切,极易出现乱码 - 不可索引、无缓存:每次查询都实时计算,数据量大时 CPU 拉满,且无法利用任何索引加速
- 驱动兼容性差:某些 Node.js 或 Python 驱动版本会静默丢弃该表达式,或直接报
"invalid operator"
聚合阶段真正该做的两件事
聚合不是业务逻辑执行器,而是数据流的边界控制器。它的安全职责很窄:
- 用
$project显式剔除敏感字段:{"ssn": 0, "phone": 0},防止意外泄露 - 配合数据库视图定义脱敏接口:
db.createView("user_public", "users", [{$project: {_id: 1, name: 1, email: 1}}]),再给前端用户分配只读该视图的权限 - 禁止在视图定义中塞
$substr类逻辑——这等于把掩码规则硬编码进 schema,改个格式就得发 DB 变更,审计和回滚都困难
应用层掩码才是唯一可控路径
所有输出到外部(API、报表、日志)的数据,必须经过统一掩码函数处理:
- Go/Python/Node.js 中封装
maskIdNumber(),输入原始字符串,输出"***-**-1234",逻辑集中、可单元测试、可灰度开关 - Laravel-MongoDB 模型用
$hidden = ['id_number']控制序列化,但注意这只是 JSON 层过滤,不防内存泄露 - 导出类场景(如后台报表):聚合只提取原始字段,服务端拿到游标后逐条调用掩码函数,不把逻辑塞进
aggregate()管道
最容易被忽略的一点:即使你用了企业版字段级加密(FLE),客户端仍需密钥解密才能读明文;而掩码是单向展示需求,和加密无关。真正的防线在协议层——比如 CVE-2025-14847 这类堆内存泄露漏洞,发生在 Zlib 解压入口,远早于任何聚合逻辑,此时字段掩码完全无效。











