我上了年纪,电脑水平一般,用 AI 助手干活有两个月了。前面写了几篇怎么让它替我填表单、怎么让它出报告,这篇换个方向——讲一件更吓人的事:AI 帮你写出来的东西,本身可能是新的泄密口。
这话不是吓唬人,是我自己踩出来的。
我平时让 AI 帮我写点小脚本,抓数据、填表、生成报告这类。有一次脚本跑完,终端里刷出来一行日志,是它自己加的调试输出,长这样:
已加载凭据:开头 eyJ,后面跟着一百多个看不懂的字符
后面还跟着一长串。我当时没反应过来,还挺高兴,觉得它干得挺细致。后来才想明白:那串东西叫 accessToken,等同于账号密码。 谁拿到它,就能以我的身份去调接口、花我的额度、看我的会话记录。
更要命的是,那次我正在写一篇准备发到公开社区的文章,正文里要贴一段脚本。AI 很贴心地把我本地脚本"脱敏"了一遍再贴进去——它把 token 改成了「前 15 位 + 省略号 + 后 8 位」。看着挺专业对吧?其实这等于没脱。后 8 位虽然不能直接用,但配上前面那段,就是给人家递了个把手;再说,把"我们内部是这么处理的"写进公开文章,等于教别人犯错。
那天这篇稿子最后没发出去,是我自己又读了一遍捞出来的。事后我一身冷汗:如果那天我懒一点,直接点了发布呢?
第一种:自己用眼睛看。
最开始的办法。看一次要十几分钟,眼睛在几百行里找几个可疑词,跟大海捞针一样。而且看着看着就麻了,越看越觉得"应该没问题"。人眼做这种重复筛查,天生就不靠谱。
第二种:让 AI 自己检查它写的东西。
我试过跟它说"把全文检查一遍,有没有敏感信息"。它每次都回"检查完毕,未发现问题",态度还特别好。
这个办法有个致命的逻辑错误:让它检查它自己,等于让嫌疑犯自查。 它不是不认真,是它的注意力在语义上,不在字符上——它觉得"前 15 后 8"已经是脱敏了,所以报没问题。它没错,是它的标准跟我想要的不一样。
第三种:写个正则扫一遍正文。
这个进了一大步,我用了好几天。但扫到第三次就发现一个大窟窿:
正则扫的是正文,扫不到代码块。
我那篇文章里嵌了四段 Python。正文里的敏感词我全清干净了,可代码块里还留着旧版本的实现——就是那个"只露前 15 后 8"的脱敏函数。正文的扫描规则够不着它。结果正文干干净净,代码里明明白白写着怎么泄露。
这就是我最想说的一点:只要你的文章里有代码,代码就是独立的风险面,得单独过一遍。
我现在的规矩是:任何要发出去的东西——文章、代码、日志、报错信息——落盘之后、发出去之前,先跑一遍这个脚本。
几个原则我先说清楚,这些比代码本身重要:
脚本拆成五关:
关卡,查什么,为什么
第 1 关,中文字数(不含代码块),平台有字数门槛,不够直接不过审
第 2 关,代码块围栏配对、语言标识,围栏落单会把后半个页面全吞成代码
第 3 关,脱敏扫描,正文和代码块分别报,代码块是独立风险面
第 4 关,占位符残留,留着"(此处配图)"的文章看着就残
第 5 关,头尾标签,平台靠标签认活动文章,少了不计分
核心代码在下面,直接复制就能用。
# -*- coding: utf-8 -*-
"""
外发自检 —— 发布/分享之前跑一遍
用法: python 外发自检.py 你的文件.md
零依赖,只读,不联网,不改动任何文件。
"""
import re, sys, json, os
FENCE = chr(96) * 3 # 三个反引号组成的代码围栏标记(用变量,避免在正文里出现裸字符)
# 规则放外部文件,脚本本身不写具体敏感词
RULES_FILE = "外发规则.json"
DEFAULT_RULES = {
"系统用户名路径": r"[A-Za-z]:\\+Users\\+[^\\\s\"']+",
"手机号": r"(?<!\d)1[3-9]\d{9}(?!\d)",
"邮箱": r"[\w.+-]+@[\w-]+\.[\w.]+",
"令牌JWT": r"eyJ[A-Za-z0-9_-]{10,}",
"键值型密钥": r"(?i)(api[_-]?key|secret|passwd|password|token)\s*[:=]\s*['\"][^'\"]{8,}"
}
PLACEHOLDERS = ["此处配图", "待补充", "TODO", "XXX占位", "(图)"]
def load_rules():
if os.path.exists(RULES_FILE):
return json.load(open(RULES_FILE, encoding="utf-8"))
return DEFAULT_RULES
def split_code(text):
"""把正文和代码块拆开,分别返回。"""
body, code, in_code = [], [], False
for line in text.splitlines():
if line.strip().startswith(FENCE):
in_code = not in_code
continue
(code if in_code else body).append(line)
return "\n".join(body), "\n".join(code)
def main(path):
if not os.path.exists(path):
print("文件不存在:", path); return 1
text = open(path, encoding="utf-8").read()
body, code = split_code(text)
rules = load_rules()
fail = []
# 第 1 关 中文字数(不含代码块)
cn = len(re.findall(r"[\u4e00-\u9fff]", body))
print(f"[1] 中文字数(不含代码):{cn}", "OK" if cn >= 1500 else "不足")
if cn < 1500: fail.append("字数不足")
# 第 2 关 围栏配对
fences = re.findall("^" + FENCE + r"(\w*)\s*$", text, re.M)
opens = [f for i, f in enumerate(fences) if i % 2 == 0]
if len(fences) % 2:
print("[2] 围栏数为奇数,有落单的围栏"); fail.append("围栏未配对")
elif any(f == "" for f in opens):
print("[2] 有代码块没标语言"); fail.append("缺语言标识")
else:
print(f"[2] 代码块 {len(opens)} 段,配对正确")
# 第 3 关 脱敏(正文、代码分开报)
for name, pat in rules.items():
in_body = len(re.findall(pat, body))
in_code = len(re.findall(pat, code))
if in_body or in_code:
fail.append(f"{name}:正文 {in_body} 处 / 代码 {in_code} 处")
print("[3] 脱敏:", ";".join(fail) if fail else "干净")
# 第 4 关 占位符
ph = [p for p in PLACEHOLDERS if p in text]
print("[4] 占位符残留:", ph if ph else "无")
if ph: fail.append("有占位符")
# 第 5 关 头尾标签
first = text.splitlines()[0] if text.splitlines() else ""
last = [l.strip() for l in text.strip().splitlines() if l.strip()][-1]
tag_ok = first.endswith("#WorkBuddy#") and last == "#WorkBuddy#"
print("[5] 头尾标签:", "OK" if tag_ok else "缺失")
if not tag_ok: fail.append("头尾标签缺失")
print("\n" + ("=" * 40))
print("结论:", "可以发" if not fail else "先别发,上面这些还没过")
return 0 if not fail else 2
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else ""))
第一版脚本我写得很实在,把要查的东西直接列在代码里:真实昵称、常住的城市名、用过的平台账号、内部的项目代号,一行一个,清清楚楚。
用了没两天我就反应过来——这个脚本我是要贴到文章里的。 词表一贴出去,等于我自己把"我叫什么、住哪个城市、买了什么"报了一遍。安检员自己成了泄密源,挺讽刺的。
改法很简单:词表从脚本里搬出去,放到一个外部的 JSON 文件,脚本只负责读。JSON 不往外发,谁也看不到。
{
"系统用户名路径": "[A-Za-z]:\\+Users\\+[^\\\s\"']+",
"令牌JWT": "eyJ[A-Za-z0-9_-]{10,}",
"你的真实昵称": "这里写只有你自己知道的词",
"你的常住地": "这里写具体的城市名",
"内部项目代号": "这里写不方便公开的名称"
}
脚本找不到这个文件,就用内置的通用规则(手机号、邮箱、令牌、键值型密钥那几条);找到了就并上你自己的。这样脚本能公开,家底不公开。
顺带说一句,这个坑还有个变种:日志。 脚本跑的时候别把命中内容打印出来,只报"第几行、哪一类、命中几处"就够了。我见过不少脚本,扫出问题就把原文整段 echo 出来——本来只有一处泄密,一打印变成两处。
坑,表现,怎么躲
让 AI 检查它自己,每次都回"未发现问题",检查必须独立于生成,用本地脚本
只扫正文不扫代码,旧的不安全实现藏在代码块里,正文和代码块分开统计、分开报
只露"前 15 后 8",看着像脱敏,实际仍属泄露,要么全隐藏,要么只报"已加载"
敏感词写在脚本里,脚本一贴,家底全出去了,词表放外部 JSON,脚本只读不写死
日志打印命中内容,扫一次泄一次,只报行数与类型,不打印原文
靠眼睛看,看十几分钟就麻了,交给脚本,人只做最后的判断
算笔账。写这个脚本我花了大概四十分钟,中间还返工了一次(就是词表泄密那次)。
从那以后,每次外发前跑一遍,五秒钟出结果。这两个多月我发了七八篇东西,跑过二十来次自检,抓出来过四次真问题:两次是代码块里的旧实现,一次是文章里混进了系统用户名路径,一次是字数不够。
四次里面任意一次漏出去,后面的麻烦都比四十分钟大得多——尤其是用户名路径那种,发出去就收不回来了。
更实在的收益是心态。以前每次点发布之前我都要犹豫半天,生怕哪里没看清;现在跑一遍脚本,看到"可以发"三个字,手就敢按下去了。把不确定变成确定,这是这个脚本最大的用处。
用 AI 这段时间,我最大的体会不是它有多能干,而是它的能干和它的风险是同一件事的两面。它能帮你写出一堆脚本,也就意味着它能把不该写的东西写进去;它记得住你的习惯和偏好,也就意味着它手里握着你的信息。
我不是说不能用,我自己天天在用。我是说,越顺手的工具,越要在出口处加一道闸。 这道闸不用多复杂,一个几十行的本地脚本就够,关键得是独立的、确定的、不联网的。
如果你也经常把 AI 写的东西发出去,建议照着这个思路弄一个。不用抄我的代码,按你自己的情况改——你怕什么漏出去,就把什么写进那张词表里。
最后提醒一句:脚本跑出来"干净",不代表可以不过脑子。 它只能查你事先想到的东西,想不到的它查不出来。最终拍板的还得是人。
�
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。