1
00:00:00,100 --> 00:00:06,432
本内容改编自小山学堂《学 AI 产品，从入门到精通》，为二次演绎配音版。

2
00:00:06,382 --> 00:00:13,149
这一集讲两件事，一件是网络和凭据，一件是给模型设计的补丁格式。

3
00:00:13,149 --> 00:00:19,002
它们的共同点是，都在处理同一件事，模型这一侧看得见什么。

4
00:00:19,002 --> 00:00:20,516
先看网络。

5
00:00:20,516 --> 00:00:26,586
你让模型去代码托管平台提一个 issue，它写出 curl，环境里有真 token。

6
00:00:26,586 --> 00:00:32,500
下一秒目标域名换成恶意站点，或者打到云厂商的元数据地址。

7
00:00:32,500 --> 00:00:35,997
同一份能力，两种完全不同的后果。

8
00:00:35,997 --> 00:00:39,915
Codex 的答案是四道关卡加一个凭据经纪。

9
00:00:39,915 --> 00:00:51,153
域名怎么判有固定顺序，真密钥根本不进子进程的环境，模型 printenv 看见的是一串形状相同的假值，出站那一刻代理才把它换回真的。

10
00:00:51,153 --> 00:00:53,221
后半场换到补丁。

11
00:00:53,221 --> 00:00:59,771
模型改一个函数，吐出标准 unified diff，把起始行写成四十七、跨度写成三。

12
00:00:59,771 --> 00:01:04,194
可是文件刚才被别处插了两行，这四个数字一起作废。

13
00:01:04,194 --> 00:01:12,740
老工具会按行号去找，找不到就在附近 fuzz，最后可能把改动贴到邻近的另一个函数上，测试还全是绿的。

14
00:01:12,740 --> 00:01:22,608
Codex 的答案是给模型一种完全不同的语法，头一行不放行号，只放一句上下文锚点，定位交给运行时的字符串搜索。

15
00:01:22,608 --> 00:01:25,216
先剧透一下这一集的主线。

16
00:01:25,216 --> 00:01:35,564
前半场和后半场都在做同一件事，把模型不擅长的精确性拿走，放到运行时去做，同时给模型留一份它能看懂的回执。

17
00:01:35,564 --> 00:01:40,216
域名判定是这样，凭据是这样，补丁格式也是这样。

18
00:01:40,216 --> 00:01:43,317
两件事连起来看，核心是一条。

19
00:01:43,317 --> 00:01:49,519
给模型的东西要适合模型填写，真正精确的部分放在运行时执行。

20
00:01:49,519 --> 00:01:50,829
开始。

21
00:01:50,980 --> 00:01:52,841
先看出站判定。

22
00:01:52,791 --> 00:01:56,781
最容易做错的版本是只拦不在名单里的域名。

23
00:01:56,781 --> 00:02:02,202
那样做有两个漏洞，deny 条目会被绕过，内网地址会漏过去。

24
00:02:02,202 --> 00:02:08,031
还有一个更隐蔽的，空名单如果默认放行，忘写配置等于全开。

25
00:02:08,031 --> 00:02:10,723
所以判定顺序必须写死。

26
00:02:10,723 --> 00:02:13,283
第一步先判 deny，它恒胜。

27
00:02:13,283 --> 00:02:15,880
第二步判本地或者私有地址。

28
00:02:15,880 --> 00:02:18,103
第三步才走 allow 名单。

29
00:02:18,103 --> 00:02:19,894
两条关键规则。

30
00:02:19,894 --> 00:02:23,668
名单为空，或者没有命中，一律拦。

31
00:02:23,668 --> 00:02:29,617
决策器只能翻 allow 名单漏掉的案，翻不了 deny 清单，也翻不了本地网段。

32
00:02:29,617 --> 00:02:37,803
这个分工很重要，它可以让你给模型留一点请求放行的余地，同时保证真正的红线永远不会被翻。

33
00:02:37,803 --> 00:02:40,639
再看通配和解析这两个坑。

34
00:02:40,639 --> 00:02:46,553
星号点 example 点 com 不含它的 apex 域名，要匹配 apex 得写两个星号。

35
00:02:46,553 --> 00:02:53,620
更关键的是解析结果，域名如果解析到私有地址，哪怕精确写进名单也会被拦。

36
00:02:53,620 --> 00:03:00,351
本地字面量也一样，必须精确写上 localhost 或者本机回环地址，通配星号不算。

37
00:03:00,351 --> 00:03:04,930
这几条规则背后有一个共同原则，内网要显式开门。

38
00:03:04,930 --> 00:03:13,656
默认拒绝、deny 恒胜、内部网段不能靠通配蒙混过关，这是服务器端请求伪造防护的通用形状。

39
00:03:13,656 --> 00:03:22,718
换语言重写，该问的还是同一组问题，没写名单时怎么办，冲突时谁赢，解析到内网的名字算不算放行。

40
00:03:22,718 --> 00:03:24,942
最后一条优先级规则。

41
00:03:24,942 --> 00:03:30,206
同一个 pattern 既出现在 allow 又出现在 deny，有效值取大的那个。

42
00:03:30,206 --> 00:03:33,668
枚举序已经把 deny 排在最后面。

43
00:03:33,820 --> 00:03:40,645
被拦住之后怎么回应，这一步经常被忽略，但它直接决定模型下一步做什么。

44
00:03:40,595 --> 00:03:47,205
Codex 返回给命令进程的是四百零三，附带一个自定义头部，正文是一句人话。

45
00:03:47,205 --> 00:03:50,078
这里有处设计细节值得注意。

46
00:03:50,078 --> 00:03:57,914
不在名单和沙箱拦了本地，这两种情况对外暴露的错误码名字是同一个，但正文写得不一样。

47
00:03:57,914 --> 00:04:07,229
这么做的理由是，错误码给机器看，正文才能区分「这个域名不在名单里」和「沙箱把本地地址拦了」这两种完全不同的处境。

48
00:04:07,229 --> 00:04:09,116
最关键的是后续。

49
00:04:09,116 --> 00:04:14,921
这条四百零三会作为命令输出喂回模型，采样循环不会停。

50
00:04:14,921 --> 00:04:22,878
也就是说这是一个可恢复的错误，模型看得到原因，可以改主意换个域名或者请用户授权。

51
00:04:22,878 --> 00:04:24,849
这个差别很重要。

52
00:04:24,849 --> 00:04:28,852
出网策略写错了，模型还能凭借回执调整。

53
00:04:28,852 --> 00:04:34,140
但凭据一旦进了模型上下文，后面每一层脱敏都是在补救。

54
00:04:34,140 --> 00:04:36,688
两者成本差着数量级。

55
00:04:36,840 --> 00:04:41,405
接着看凭据这一关，这是全片最值得抄的一段设计。

56
00:04:41,355 --> 00:04:42,929
问题很直接。

57
00:04:42,929 --> 00:04:49,901
token 跟着命令进会话记录，下一轮模型还能看见它，再下一轮可能进日志。

58
00:04:49,901 --> 00:04:55,237
凭据一旦进入模型上下文，后面每一层处理都是补救措施。

59
00:04:55,237 --> 00:04:57,761
Codex 的做法是分两步。

60
00:04:57,761 --> 00:05:05,802
第一步，拉起命令之前，凭据经纪把环境变量里的那些密钥换成同长度、同前缀的假值。

61
00:05:05,802 --> 00:05:09,312
模型如果执行 printenv，看见的是假的。

62
00:05:09,312 --> 00:05:13,218
这份假值进了上下文，对上游完全没用。

63
00:05:13,218 --> 00:05:19,576
假值保持形状，是为了让那些启动时会校验格式的客户端还能正常起来。

64
00:05:19,576 --> 00:05:21,559
第二步发生在出站。

65
00:05:21,559 --> 00:05:28,939
中间代理按 host 过滤，请求头里必须带着那个假值，才把它换成经纪内存里存的真 token。

66
00:05:28,939 --> 00:05:32,569
为什么要把真值拿走而不是只做脱敏。

67
00:05:32,569 --> 00:05:35,093
因为撤销成本完全不对等。

68
00:05:35,093 --> 00:05:40,514
出网策略写错了，模型看到回执还能改，最坏是一轮重来。

69
00:05:40,514 --> 00:05:50,718
凭据进了上下文，它会进会话记录、进日志、进下一轮提示词，你要在每一层都补一道脱敏，而且永远不确定有没有漏。

70
00:05:50,718 --> 00:05:56,547
所以正确做法是在源头就拿走，回填只发生在代理进程的内存里。

71
00:05:56,547 --> 00:05:58,146
一句话总结。

72
00:05:58,146 --> 00:06:02,677
子进程口袋里是假票根，柜台后面锁着真票。

73
00:06:02,677 --> 00:06:07,220
假值走完全程可见面，真值只在出站那一跳出现。

74
00:06:07,220 --> 00:06:09,324
再补一个横向对照。

75
00:06:09,324 --> 00:06:19,396
另一个 harness 把密钥做成引用，配置文件里只存环境变量名，值在每次操作时才解析，所以配置表面从不出现真值。

76
00:06:19,396 --> 00:06:24,360
它还有一层子进程擦除，名字里带关键词的键会被丢掉。

77
00:06:24,360 --> 00:06:32,533
这挡得住配置文件泄露，但如果拉起进程时没走这层擦除，模型 printenv 仍然可能看见真 token。

78
00:06:32,533 --> 00:06:38,278
对照着看，Codex 的做法是从源头换成假值，覆盖面更靠前。

79
00:06:38,278 --> 00:06:40,982
这里还有两条边界要说清楚。

80
00:06:40,982 --> 00:06:44,624
经纪只覆盖 GitHub 和 OpenAI 两个上游。

81
00:06:44,624 --> 00:06:52,064
另外，用户自己改过的环境值不会被还原，那些标记一律当作不可信输入处理。

82
00:06:52,200 --> 00:06:54,241
第三道关卡是沙箱。

83
00:06:54,191 --> 00:06:58,350
为什么要它，因为应用层代理有个致命弱点。

84
00:06:58,350 --> 00:07:02,688
Go 的标准库对回环地址会绕过代理环境变量。

85
00:07:02,688 --> 00:07:06,198
你以为流量进了代理，其实它直连本机。

86
00:07:06,198 --> 00:07:09,996
而本机可能开着管理口或者容器套接字。

87
00:07:09,996 --> 00:07:15,561
这时候策略文件写了再多名单也没用，请求根本没走到那一层。

88
00:07:15,561 --> 00:07:19,660
所以下一层必须是操作系统或者防火墙。

89
00:07:19,660 --> 00:07:22,364
做法是只放行代理端口。

90
00:07:22,364 --> 00:07:27,496
Mac 上的配置只写本地到那个端口，Linux 走仅代理模式。

91
00:07:27,496 --> 00:07:30,525
还有一个开关叫允许本地绑定。

92
00:07:30,525 --> 00:07:37,532
它为假的时候，不走代理的环境变量被写成空串，回环字面量也必须走代理。

93
00:07:37,532 --> 00:07:42,075
把它打开，等于承认回环流量不再经过名单检查。

94
00:07:42,075 --> 00:07:46,667
再看横向对照，另一个产品把阀门装在工具入口。

95
00:07:46,667 --> 00:07:54,828
它的网页抓取在工具内部做域名名单和解析后 IP 检查，空名单拦所有地址，方向和 Codex 一致。

96
00:07:54,828 --> 00:08:02,761
但它的检查明确放行回环地址，注释写着本地开发，而且命令行里的 curl 完全不走这张名单。

97
00:08:02,761 --> 00:08:05,693
差别就在于阀门装在哪一层。

98
00:08:05,693 --> 00:08:13,013
装在工具入口，管不到子进程；装在所有子进程出口，就必须和沙箱绑在一起。

99
00:08:13,013 --> 00:08:15,417
一句话记住这个结构。

100
00:08:15,417 --> 00:08:22,508
应用层代理挡不住不肯走代理的客户端，下一层必须是只留一个口的操作系统防线。

101
00:08:22,508 --> 00:08:29,828
三道门叠在一起才构成托管网络，单独任何一道都挡不住另外两道漏掉的面。

102
00:08:29,980 --> 00:08:32,141
后半场换补丁格式。

103
00:08:32,091 --> 00:08:35,024
先看标准格式为什么会翻车。

104
00:08:35,024 --> 00:08:41,550
diff 头里面那四个数字必须同时填对，旧起始、旧跨度、新起始、新跨度。

105
00:08:41,550 --> 00:08:46,755
模型是在带行号的摘录里数的，写的时候还要自己加算。

106
00:08:46,755 --> 00:08:52,367
文件被别处插两行，四个数字一起错，这是常态，不是意外。

107
00:08:52,367 --> 00:08:54,555
更麻烦的是失败方式。

108
00:08:54,555 --> 00:08:58,125
老工具会按行号去找，找不到就 fuzz。

109
00:08:58,125 --> 00:09:04,255
fuzz 再失败整份作废，可它也可能成功的把变更贴到邻近的另一个函数上。

110
00:09:04,255 --> 00:09:08,161
这时候测试还是绿的，只是改错了函数。

111
00:09:08,161 --> 00:09:10,625
这比明确报错糟糕得多。

112
00:09:10,625 --> 00:09:14,399
Codex 的答案是把填写和阅读彻底拆开。

113
00:09:14,399 --> 00:09:22,944
模型填的那份没有行号，更新一段时头一行只写锚点文本，单独一个标记表示从当前位置继续搜。

114
00:09:22,944 --> 00:09:29,002
给模型的文法里没有任何行号的位置，两种写法都只带文本锚点。

115
00:09:29,002 --> 00:09:32,920
这份格式还有个附加好处，它足够小。

116
00:09:32,920 --> 00:09:40,288
发给模型的说明书把它定义成精简的、面向文件的、易解析也易安全应用的格式。

117
00:09:40,288 --> 00:09:46,803
新增、删除、移动在文法里是三种标记，解析器按标记分发。

118
00:09:46,803 --> 00:09:53,690
模型不用去记文件头、空设备路径和重命名头怎么拼，少记一样就少错一样。

119
00:09:53,690 --> 00:09:58,678
人看变更的时候，界面再另外生成一份标准 unified diff。

120
00:09:58,678 --> 00:10:02,404
工具参数里那份格式到这里已经用完了。

121
00:10:02,404 --> 00:10:11,430
给模型的说明书把这套语言定义得很克制，只支持新增、删除、移动三种标记，解析器按标记分发。

122
00:10:11,430 --> 00:10:16,839
模型不用去记那些文件头、空设备路径和重命名头怎么拼。

123
00:10:16,839 --> 00:10:18,521
一句话概括。

124
00:10:18,521 --> 00:10:21,418
坐标靠加算，内容靠识别。

125
00:10:21,418 --> 00:10:27,211
把找到哪一段从填写时的算术，改成应用时的字符串搜索。

126
00:10:27,360 --> 00:10:28,908
再看容错。

127
00:10:28,858 --> 00:10:37,187
模型写补丁时行尾多一个空格，或者把文件里的短横写成了别的 Unicode 短横，这都属于高频事故。

128
00:10:37,187 --> 00:10:42,488
如果每次都整份失败，模型只能重写一遍，白白多一轮。

129
00:10:42,488 --> 00:10:47,656
但如果我按行号附近再试几行，又回到 fuzz 贴错函数的老路。

130
00:10:47,656 --> 00:10:51,094
所以做法是按四档从紧到松搜。

131
00:10:51,094 --> 00:10:53,341
第一档整行精确相等。

132
00:10:53,341 --> 00:10:56,118
第二档去掉行尾空白再比。

133
00:10:56,118 --> 00:10:58,413
第三档两边都做 trim。

134
00:10:58,413 --> 00:11:02,752
第四档把常见 Unicode 短横和弯引号收成 ASCII。

135
00:11:02,752 --> 00:11:09,543
四级都失败就返回空，报找不到上下文，没有「按附近几行再试一次」这个循环。

136
00:11:09,543 --> 00:11:12,800
这里有一条边界决定了容错的分界。

137
00:11:12,800 --> 00:11:15,144
容错要分清两类差异。

138
00:11:15,144 --> 00:11:19,675
行尾空格、弯引号是无意义的字节差，可以归一。

139
00:11:19,675 --> 00:11:25,156
行号偏了两行那是贴错地方，应该直接报错让模型重写。

140
00:11:25,156 --> 00:11:26,923
横向对照一下。

141
00:11:26,923 --> 00:11:37,187
另一个产品的模糊匹配只覆盖引号，先精确搜，再把弯引号收成直引号搜，没有行尾空白那一档，也没有短横归一化。

142
00:11:37,187 --> 00:11:46,418
想少一次工具往返，可以抄 Codex 的四级；想让模型收到更具体的错误信息，可以抄它那几条带错误码的拒绝文案。

143
00:11:46,418 --> 00:11:48,173
两边各有取舍。

144
00:11:48,173 --> 00:11:50,096
注意归一化的边界。

145
00:11:50,096 --> 00:11:57,332
全角引号不在那张表里，模型写了全角左引号而文件里是半角，四级都会失败。

146
00:11:57,332 --> 00:12:02,728
这条边界要如实说，不要让人以为它能兜住所有排版差异。

147
00:12:02,880 --> 00:12:05,426
最后一个话题，原子性。

148
00:12:05,376 --> 00:12:12,082
一份补丁里有两块改动，第二块对不上，第一块已经改进去了，文件变成半成品。

149
00:12:12,082 --> 00:12:17,683
排查的人看到的是应用成功了一半的文件，比整份失败更难修。

150
00:12:17,683 --> 00:12:20,304
单文件内部是有保证的。

151
00:12:20,304 --> 00:12:30,832
所有替换先在内存里算完，收成一个替换列表，任何一块对不上就立刻返回错误，根本走不到写盘那一步，这个文件保持原样。

152
00:12:30,832 --> 00:12:36,157
另外更新多个块的时候，块必须按在文件中出现的先后排列。

153
00:12:36,157 --> 00:12:38,705
跨文件完全是另一回事。

154
00:12:38,705 --> 00:12:45,556
按块顺序逐个写盘，失败时带着已经提交的变更返回，循环里没有回滚。

155
00:12:45,556 --> 00:12:53,645
测试把这条钉死了，先成功新增一个文件，再更新一个不存在的路径，磁盘上那个新增的文件还在。

156
00:12:53,645 --> 00:12:55,700
还有一处对照值得记。

157
00:12:55,700 --> 00:13:06,157
另一个 harness 要求先观测过文件才能改，没观测过直接抛错，观测记的是设备号、inode、大小、修改时间拼出来的版本，不是内容哈希。

158
00:13:06,157 --> 00:13:11,686
过了这道门还要求原文只出现一次，多处命中就抛歧义错误。

159
00:13:11,686 --> 00:13:20,123
Codex 没有先读约束，定位信息写在补丁里由运行时现搜，原文出现两次时取第一处，不报歧义。

160
00:13:20,123 --> 00:13:25,472
两种取舍，一个把防错挂在门禁上，一个把定位挂在补丁里。

161
00:13:25,472 --> 00:13:27,539
所以说法要准确。

162
00:13:27,539 --> 00:13:33,356
没有部分成功这个保证，只对单个文件成立，不要把它说成全局保证。

163
00:13:33,356 --> 00:13:35,929
再把两种费用摆在一起看。

164
00:13:35,929 --> 00:13:40,784
要不要跨文件事务是产品选择，不是格式本身的承诺。

165
00:13:40,784 --> 00:13:45,520
算完再提交的范围，必须和你能原子处理的单位对齐。

166
00:13:45,520 --> 00:13:56,253
一个文件可以在内存里算完再写一次，多个文件已经落盘，回滚就要再写一遍，还得处理移动这种源和目标都动过的半成功。

167
00:13:56,400 --> 00:13:59,679
最后收一下，六条能带走的东西。

168
00:13:59,629 --> 00:14:04,845
第一，出站默认拒绝，deny 恒胜，内网必须精确开门。

169
00:14:04,845 --> 00:14:09,821
这三句话是 SSRF 防护的通用形状，换语言也一样。

170
00:14:09,821 --> 00:14:13,102
第二，决策器只留给它一个能翻的案。

171
00:14:13,102 --> 00:14:18,331
让模型能推翻「不在名单」，但永远翻不了 deny 和本地网段。

172
00:14:18,331 --> 00:14:21,360
留余地和守红线可以同时做到。

173
00:14:21,360 --> 00:14:24,905
第三，真密钥不要进模型可见的环境。

174
00:14:24,905 --> 00:14:28,836
spawn 前换成同形状假值，出站再换。

175
00:14:28,836 --> 00:14:33,595
假值保持形状是为了让格式校验型客户端还能启动。

176
00:14:33,595 --> 00:14:37,850
第三点再补一条，别小看「撤回成本」这四个字。

177
00:14:37,850 --> 00:14:42,790
做安全决策时先问一句，这个东西错了能不能撤回。

178
00:14:42,790 --> 00:14:51,059
能在一条回执里撤回的，可以让模型参与决策；一旦进了上下文就撤不回的，必须在源头拦住。

179
00:14:51,059 --> 00:14:58,162
第四，应用层代理挡不住绕过它的客户端，下一层必须是只放行代理端口的沙箱。

180
00:14:58,162 --> 00:15:01,949
第五，给模型的格式要适合模型填写。

181
00:15:01,949 --> 00:15:05,963
坐标靠加算是不可靠的，内容靠识别才稳。

182
00:15:05,963 --> 00:15:10,242
空白和标点可以逐级放宽，位置绝对不猜。

183
00:15:10,242 --> 00:15:14,184
第六，说明范围的边界要和实现的边界对齐。

184
00:15:14,184 --> 00:15:19,196
单文件有原子保证就说单文件，不要顺口说成整份事务。

185
00:15:19,196 --> 00:15:22,850
这六条放在一起，讲的是同一种克制。

186
00:15:22,850 --> 00:15:28,018
把模型不擅长的部分拿走，同时把模型能看懂的回执留下。

187
00:15:28,018 --> 00:15:30,122
这一集就到这里。

