🧩 第3章 捋清楚:从任务到技能五步走
📄 一句话概括

技能是别人写的程序,装进你的机器。它可能删文件、外发数据、写死密钥。 三道闸(装之前、跑之前、出事之后)、四个习惯、一张红线清单,这一篇全给。 后半段写一个真的扫描器,把六份示例脚本扫一遍,看它能不能看出意图。

📝 本章和前面几章的关系

这一篇是第 3 章最后一节,也是最重要的一节。前面讲的是「怎么做好」,这一篇讲「怎么不出事」。 第 1 章提到过「钥匙不进技能包」,这里把它展开成一套能执行的做法。 第 6 章讲发布时会回来用这里的红线清单:发给别人的技能,得先过自己这一关。

一、风险从哪来

先把风险说具体。技能不是数据,是会被执行的东西——哪怕它只是一份文本。 文本本身无害,但文本里会写「运行 scripts/xxx.py」。那个脚本就有害了。 所以风险有三层,一层比一层深。 第一层,读:技能被选中时读进来的正文、参考文档,可能带着你的私有信息一起进了上下文。 第二层,跑:技能点名的脚本在你的机器上执行,能动你的文件、能发网络请求。 第三层,传:技能是要分发的。你写的东西会到别人手里,别人的东西也会到你手里。 三层里最危险的是第二层,因为它不可逆。删掉的文件回不来,发出去的数据收不回。

二、三道闸

第一道,装之前:读一遍正文与它引用的脚本。 这一步挡掉九成的坑。读不懂就不装——「读不懂」本身就是最强的信号。 第二道,跑之前:把写操作限定在一个目录里。 具体到操作上就一句话:别在 home 目录下跑别人的脚本,在一个专门建的空目录里跑。 这样即使它乱删,最多删掉那个空目录里的东西。 第三道,出事之后:能回滚。 进 git、或者先备份、或者在一个能整个删掉重来的容器里跑。 三道闸里,第三道最少人做,也最救命。前两道靠判断,第三道只需要花两分钟。

三、四个好习惯

习惯一:装之前读脚本。 读不懂就不装。这条没有例外。 习惯二:密钥只从环境变量读。 技能包里只写变量名,永远不写值。 习惯三:技能在自己的仓库里跑。 别在装着重要东西的目录里跑别人的技能。 习惯四:第一次跑之前先备份,或者确认能回滚。 这四条都不难,难在坚持。出事的人九成不是不知道,是「当时图省事」。 习惯二值得单独强调:技能是要发给别人的,文件里写死一把钥匙,等于连钥匙一起发出去。 而且删掉那一行也不算完——git 历史里还在。这一条后面第 6 章会再讲一次。

四、安全红线清单

装技能之前对着这张表扫一眼,看到哪一条就停下来问一句为什么。 删文件:任何形式的批量删除。删了不可逆,这是唯一没有后悔药的一类。 外发:把本地内容发到外网。哪怕是「上报使用统计」,也要看清楚发的是什么。 密钥:明文写在文件里。不管真假,都不该出现在技能包里。 执行:eval、exec、shell=True 拼用户输入。输入可控就是任意代码执行。 提权:sudo、管理员权限。一个技能需要提权,先问它要干什么。 改系统:改环境变量、改配置文件、装服务。这些改动会在你忘了它之后还留着。 六条里前四条最常见,后两条最少见但后果最重。

五、规则扫得出形式,扫不出意图

可以用脚本把技能里的危险写法扫一遍。这一篇的 Demo 就干这件事:六条规则,扫六个脚本。 但要说清楚它的边界:它只能扫「形式」,扫不出「意图」。 os.remove 出现在一个清理构建产物的脚本里,是正常的; 出现在一个「整理阅读记录」的技能里,就很可疑。 同样一行代码,意图完全不同。这一点任何静态规则都做不到。 所以扫描器的定位不是「替你做决定」,是「把需要看一眼的地方挑出来」。 它把一百个文件筛到五个,你看那五个。剩下九十五个不用看。 这个价值已经很够了——人的注意力是稀缺的,扫描器省的就是注意力。

六、本章小结

第 3 章给的是动手之前的思考顺序,五步:拆任务、判做不做、分人机、选工具、守安全。 拆任务是为了看清「这活到底由几步组成」,拆完自然知道哪步能交给脚本。 判做不做用三问四限打分:重复、有明确标准、出错代价低,三条都占才值得做。 分人机的核心是别把需要判断的活塞给脚本,也别把体力活留给模型。 选工具的顺序是反的:先看手头有什么,再找现成的,最后才装。 守安全是这五步里唯一不能打折的一步——前四步做错了损失时间,这一步做错了损失数据。 这五步不需要每次都走完。写第二个技能的时候,你会发现前三步已经变成条件反射。 下一章开始真写:三个技能,三种形态,一次把「怎么落成文件」走完。

七、动手 Demo:把技能里的脚本扫一遍,看它想干什么

这一段写一个六条规则的扫描器,然后拿六份示例脚本各跑一遍。 六份里有一份是干净的,另外五份各埋了一种典型风险——从删文件到写死密钥。

RULES = [
    ('硬删除', r'\bos\.remove\b|\bshutil\.rmtree\b|rm\s+-rf', 3),
    ('外发数据', r'urlopen|requests\.(get|post)|socket\.', 3),
    ('疑似密钥', r'sk-[A-Za-z0-9-]{8,}|ghp_[A-Za-z0-9]{8,}', 3),
    ('动态执行', r'\beval\(|\bexec\(', 3),
]

score = sum(w for _n, rx, w in RULES if re.search(rx, code))
verdict = '别装' if score >= 5 else ('看一眼' if score else '安全')

实跑输出:

--------------------------------------------------------------------
把技能里的脚本扫一遍,看它想干什么
--------------------------------------------------------------------


  ① 六条规则,先摆出来
  --------------------------------------------------------------------
| 规则 | 认这几个词 | 风险分 | 为什么 |
|---|---|---:|---|
| 硬删除 | os.remove、rmtree、rm -rf | 3 | 删文件,删了不可逆 |
| 执行外部命令 | subprocess、os.system | 2 | 命令里带变量就等于远程执行 |
| 外发数据 | urlopen、requests、socket | 3 | 会把本地内容带出去 |
| 疑似密钥 | sk- 开头、ghp_ 开头 | 3 | 写死密钥等于把钥匙发出去 |
| 读环境变量 | os.environ[ | 1 | 本身没问题,但要确认读的是什么 |
| 动态执行 | eval、exec | 3 | 输入可控就是任意代码执行 |

  六条里三条是 3 分(高危):删文件、外发数据、写死密钥。
  ! 这些规则都只能扫「形式」,扫不出「意图」。3 分的要人看一眼,1 分的略过。

  ② 六个示例脚本,逐个扫
  --------------------------------------------------------------------
| 脚本 | 命中规则 | 风险分 | 结论 |
|---|---|---|---|
| safe_ok.py | — | 0 | 安全 |
| risky_rm.py | 硬删除 | 3 | 看一眼 |
| risky_shell.py | 硬删除、执行外部命令 | 5 | 别装 |
| risky_net.py | 外发数据 | 3 | 看一眼 |
| risky_key.py | 疑似密钥、读环境变量 | 4 | 看一眼 |
| risky_eval.py | 动态执行 | 3 | 看一眼 |

  六份里只有第一份是零分。其余五份都命中了至少一条。
  ! 「别装」不等于「有恶意」,而是「你要先读懂它再决定」。

  ③ 挑三份说清楚它们高危在哪
  --------------------------------------------------------------------
  risky_rm.py(3 分):硬删除
  risky_net.py(3 分):外发数据
  risky_key.py(4 分):疑似密钥、读环境变量

  risky_rm 遍历一个目录逐个删,删的是构建产物还是源码,从代码里看不出来。
  risky_net 把本地文件内容拼进 URL 发出去——这是最典型的「数据外带」。
  risky_key 里那把 sk-live 开头的字符串,不管它是真是假,都不该出现在技能包里。

  ④ 三道闸:装之前、跑之前、出事之后
  --------------------------------------------------------------------
| 闸 | 做什么 | 做不到会怎样 |
|---|---|---|
| 装之前 | 读一遍正文与脚本引用 | 不知道它要干什么,就别装 |
| 跑之前 | 把写操作限定在一个目录里 | 删也只能删那一个目录 |
| 出事之后 | 能回滚 | 进 git 或者先备份 |

  三道闸里,第三道最少人做,也最救命。
  ! 「跑之前限定目录」具体到操作上就一句话:别在 home 目录下跑别人的脚本。

  ⑤ 四个习惯与一张红线清单
  --------------------------------------------------------------------
  习惯一:装之前读脚本。读不懂就不装,这一条挡掉九成的坑。
  习惯二:密钥只从环境变量读,技能包里只写变量名。
  习惯三:技能在自己的仓库里跑,别在装着重要东西的目录里跑。
  习惯四:第一次跑之前先备份,或者确认能回滚。

| 红线 | 具体表现 |
|---|---|
| 删文件 | 任何形式的批量删除 |
| 外发 | 把本地内容发到外网 |
| 密钥 | 明文写在文件里 |
| 执行 | eval 或 shell=True 拼用户输入 |
| 提权 | sudo、管理员权限 |
| 改系统 | 改环境变量、改配置文件、装服务 |

  这张表不用背。装技能之前对着扫一眼,看到哪一条就停下来问一句为什么。

第一段是六条规则:三条 3 分(删文件、外发数据、写死密钥),一条 2 分,一条 1 分。 第二段是扫描结果:六份里只有 safe_ok.py 是 0 分。 risky_shell.py 拿了 5 分——它同时命中「硬删除」和「执行外部命令」,判「别装」。 risky_key.py 4 分,是「写死密钥」加「读环境变量」凑出来的。 第三段挑三份说清楚它们高危在哪:rm 那份看不出删的是构建产物还是源码, net 那份把本地文件内容拼进 URL 发出去,key 那份写了一把 sk-live 开头的字符串。 第四段是三道闸,第五段是四个习惯加红线清单。 扫描器只能扫形式,扫不出意图——同一行 os.remove,在不同技能里是完全不同的两件事。

30 秒实验

  1. 把你装过的第三方技能翻出来,读一遍它引用的脚本。读完还想留的,留下;读不懂的,删掉。
  2. 在你的技能目录里跑一遍上面那段扫描器,把 3 分以上的挑出来看一眼。
  3. 建一个专门跑别人脚本的空目录。以后所有「先试试看」都在那里试,试完整个删掉。

常见误区

  1. 以为「只是文本所以安全」。文本本身无害,但文本会点名脚本,脚本会执行。
  2. 把密钥写在技能里再删掉。 删掉那一行不算完,git 历史里还在,任何人 clone 之后翻 log 都能拿到。 要吊销,要用 filter-repo 重写历史。
  3. 完全相信扫描器的分数。它只能扫形式。0 分不代表安全,只代表「没命中这六条规则」。
自测三问(点开对答案) 1. 三道闸分别是什么? —— 装之前读脚本、跑之前限定目录、出事之后能回滚。第三道最少人做,也最救命。 2. 为什么密钥删掉了还不算完? —— git 历史里还在。要吊销那把密钥,再用 filter-repo 重写历史,两件事都得做。 3. 扫描器的定位是什么? —— 不是替你做决定,是把需要看一眼的地方挑出来。它省的是注意力,不是判断。

第三章完。下一章动手:真写出三个技能——写作风格、会议纪要、文章配图。

相关笔记