Python 结构化日志脱敏:递归处理敏感键,而不是盲目替换字符串

举报
yd_226410095 发表于 2026/11/04 07:25:21 2026/11/04
【摘要】 结构化日志脱敏的重点是沿 dict、list 和 tuple 递归检查约定的敏感键,而不是盲目替换所有字符串。本文给出不修改原对象的实现,并说明键名规则、自由文本、循环引用和超大对象的边界。

先沿数据结构处理,再考虑字符串

日志里混入访问令牌、密码或 Cookie,既可能造成泄露,也会增加排查问题的风险。将对象全部转成字符串后,再用正则盲目替换,容易误伤普通业务内容,也可能漏掉嵌套字段。对于已经结构化的数据,按数据结构和约定键名处理,是一个范围更清楚的起点。

下面的实现使用 Python 内置容器与标准库测试辅助:遇到 dict,检查每个键;字符串键转为小写后与敏感键集合比较;命中时直接写入 [REDACTED],不再递归原值。未命中的值继续处理。list 和 tuple 逐项递归,其他类型原样返回。

可运行实现与内存测试

from copy import deepcopy

SENSITIVE_KEYS = {"token", "password", "authorization", "cookie"}


def redact_log_value(value):
    """构造脱敏容器,不修改传入的普通容器。"""
    if isinstance(value, dict):
        return {
            key: "[REDACTED]"
            if isinstance(key, str) and key.lower() in SENSITIVE_KEYS
            else redact_log_value(item)
            for key, item in value.items()
        }
    if isinstance(value, list):
        return [redact_log_value(item) for item in value]
    if isinstance(value, tuple):
        return tuple(redact_log_value(item) for item in value)
    return value


# 全部示例值均为明显虚构值。
source = {
    "request": {
        "Token": "FAKE-TOKEN-001",
        "profile": {"PASSWORD": "FAKE-PASSWORD-002", "name": "demo-user"},
        "items": [
            {"authorization": "FAKE-AUTH-003"},
            ("keep-this", {"CoOkIe": "FAKE-COOKIE-004"}),
        ],
        "message": "自由文本 FAKE-SECRET-005 不会被本函数扫描",
    }
}
original = deepcopy(source)
redacted = redact_log_value(source)
assert redacted["request"]["Token"] == "[REDACTED]"
assert redacted["request"]["profile"]["PASSWORD"] == "[REDACTED]"
assert redacted["request"]["items"][0]["authorization"] == "[REDACTED]"
assert redacted["request"]["items"][1][1]["CoOkIe"] == "[REDACTED]"
assert redacted["request"]["items"][1][0] == "keep-this"
assert redacted["request"]["message"] == "自由文本 FAKE-SECRET-005 不会被本函数扫描"
assert source == original
assert source["request"]["Token"] == "FAKE-TOKEN-001"
print("全部断言通过")

保留键名,不改写业务对象

大小写归一只用于比较,不会改变输出键名。Token、PASSWORD、CoOkIe 都可以命中规则,输出仍保留原来的键名。敏感字段的原值无论是字符串还是容器,都会整体替换为标记,避免将原值继续送入日志。

dict、list、tuple 会构造新容器,因此不必为了脱敏而改写正在使用的业务对象。测试验证了多层字典、大小写键、列表中的字典、元组中的字典以及源对象不变。上述内存样例的全部断言已通过,未读取真实日志或使用真实凭据。

但这不是通用深拷贝:其他类型会原样返回,自定义可变对象仍可能共享引用。本例只面向约定范围内的普通容器;生产系统需要先确定日志数据模型及序列化策略。

敏感键集合需要适配实际系统

这四个键不是通用的秘密识别器。有的系统使用 access_token、secret、session_id 或自定义字段,应依据日志 schema 扩充规则,评估命名空间和嵌套约定。更稳妥的方案通常是日志白名单:只记录明确允许输出的字段,而不是事后猜测哪些字段敏感。

脱敏可作为防线之一,但不能替代日志字段设计、访问权限和存储周期管理。规则变更还应配套固定输入输出测试,防止新增字段悄悄越过原有检查。

自由文本、循环引用与大对象的边界

函数不会扫描普通字符串。示例 message 中的虚构秘密片段会保持原样,这是有意展示的边界,不代表它适合直接记录。自由文本应单独制定规则;没有可靠规则时,宁可不记录敏感上下文,也不要把一次替换当作安全保证。

示例适用于有限、无循环引用的普通结构。循环引用可能导致递归持续进行直至触发异常,生产实现应维护访问记录并明确循环引用的输出策略。深层嵌套和超大对象也需要大小、深度及耗时限制,避免日志处理本身拖慢业务。

实际使用时应先构造脱敏结果,再交给日志记录器,避免意外记录 source;同时确认记录器、异常处理分支和调试输出不会绕过该边界。结构化日志脱敏的价值不是声称覆盖所有秘密,而是让数据模型、白名单、有限规则与测试协同工作,并对遗漏范围保持诚实。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。