文章提出 prompt payload 分类框架,将内容分为普通内容、敏感数据、受限记录、机密材料四类,并在每个分支给出具体操作建议,防止机密信息被意外泄露到远程模型。
一个 checkout 服务在凌晨 02:14 告警。在岗工程师把报错堆栈、一段 application.yml 配置和最近二十条请求日志粘贴到一个免费远程沙盒里的编程 Agent。十二分钟后模型返回了一个看起来合理的 patch,同时在注释里"为了清晰起见"把那个 YAML 文件里的数据库密码又写了一遍。
问题不在模型质量,而在 payload 分类。免费远程模型和免费远程服务器让同类的失败变得很容易重复,因为这个循环很轻量,数据路径很容易被忽略。
本文是一份术语表、一棵四叶决策树,以及每片叶子上的实战示例。产出物是一个小巧的分类器,你可以在任何 Agent 会话之前运行。去掉所有产品名称,这套工作流依然成立。
按原文使用这些术语,不要把它们压缩成"敏感"这样的单个词。
Payload(负载)。远程模型可读取的 prompt 文本、附件文件、工具可见的工作区路径,以及检索片段的并集。
Secret-bearing(携带秘密)。具有访问权限的物质:密码、API token、私钥、会话 Cookie、连接字符串、签名密钥。
Restricted record(受限记录)。即使剥离了秘密也不能离场——即不能发送到你控制范围之外的物质:客户 PII、健康或金融标识符、未发布的 IP、可定位到个人的生产流量。
Internal-nonsensitive(内部非敏感)。不含秘密、不含受限记录的私有源码或日志,但并非公开。
Public reproduction(公开复现)。已在公开渠道发布的 bug 报告、开源文件,或你可以放到公开 issue 追踪器里的合成 fixture。
Remote free loop(免费远程循环)。模型、磁盘或日志运行在你无法控制的机器上的任意编程 Agent,包括免费模型和免费服务器方案。
Stand-in(替身数据)。一个最小化的伪造数据集,依然能让生产路径上的同一个测试失败。
Egress decision(出口决策)。明确选择将 payload 发送出场、保留本地,或拒绝该会话。
一个 patch 可以是公开级别的,但其 payload 可能是携带秘密的。分类的是 payload,不是意图。
按顺序走以下问题。遇到第一个"是"就停下。
payload 里是否包含携带秘密的物质,或者 Agent 能否 open() 到包含秘密的文件?
如果没有:payload 里是否包含受限记录?
如果没有:材料是否属于内部非敏感?
如果没有:它是否是公开复现?
四片叶子分别是:拒绝或脱敏、仅用替身、有边界的远程、无限制公开。
把这棵树当作一道关卡来用,而不是事后复盘。
只复制你认为任务所需的仓库切片。
用下面的搜索命令和 payload_class.py 对该切片运行分类。
在第一次 prompt 之前,在聊天标题或 PR 正文中标出叶子编号。
启动远程循环、保留本地,或拒绝。
用分类器使用的同一套秘密正则表达式扫描结果 diff。
如果第 2 步和第 3 步结论不一致,以第 2 步为准。声称的叶子是假设,扫描结果是证据。
实战示例。一个 Flask 应用启动失败。工程师想附上 .env 和 config.py,以便模型能"看到真实配置"。
# .env — 不要发送,不要挂载
DATABASE_URL=postgres://app:EXAMPLE_PASSWORD@db.internal:5432/app
STRIPE_SECRET_KEY=sk_test_example_not_real
在任何 prompt 之前运行的命令:
rg -n -i -g '!node_modules' -g '!.git' \
'BEGIN .*PRIVATE KEY|AKIA[0-9A-Z]{16}|sk_live_|sk_test_|password\s*=|SECRET|api[_-]?key' .
如果该搜索命中,则在命中项从 Agent 能看到的工作区中移除之前,不得进行远程循环。脱敏不是"在 prompt 里删掉一行,而文件依然留在挂载的免费服务器上"。把秘密移到一个本地、未挂载的路径下。在运行时从 Agent 无法列出的 env 管理器注入。
独立验证:产出物一节的分类器必须返回 secret_bearing。人类依然要审查假阴性。正则表达式是一道关卡,不是证明。
实战示例。客服粘贴了一张工单:客户邮箱、订单 ID、卡号后四位和一段堆栈跟踪。堆栈跟踪有用,其余不是。
构建一个保留失败形状的替身:
# tests/test_order_status_standin.py
from order_status import parse_event
def test_missing_shipment_id_returns_409():
event = {
"email": "user@example.test",
"order_id": "ord_000000",
"card_last4": "0000",
"shipment_id": None,
}
status, body = parse_event(event)
assert status == 409
assert body["code"] == "shipment_missing"
针对这片叶子的编号步骤:
复制失败的控流,而不是人。
用 example.test 域下的已文档化假数据替换标识符。
确认替身与生产路径在同一个断言上失败。
只发送替身、测试用例和相关源码。把工单排除在 payload 之外。
如果你无法在受限记录缺失的情况下重建失败,就不要使用免费远程循环。在与生产日志数据策略相同的机器上保留会话。
实战示例。一个内部限流器在 800ms 内对 100 个请求重复计数。日志已脱敏。无客户标识符。源码是专有的但不是秘密。
有边界的远程会话只在三个约束条件下允许:
工作区白名单。只挂载限流器包及其单元测试。不要挂载 infra/、deploy/ 或 home 目录的点文件。
保留假设。将 prompt 和工具痕迹视为持久的。如果组织策略禁止供应商日志,此叶子退化为本地-only。
输出扫描。diff 依然要通过同一套秘密正则扫描,因为模型会回显上下文。
git diff --stat
git diff -- . ':(exclude)*.pem' ':(exclude)*.env'
python3 payload_class.py --root ./limiter --allow internal_nonsensitive
这是免费远程循环真正有用的叶子。迭代成本低,爆炸半径是一个库,你可以扔掉这台机器。这不是把monorepo 其余部分"为了上下文"传上去的许可证。
披露:本文是 MonkeyCode 产品推广的一部分。MonkeyCode 提供免费模型访问和免费服务器选项;这两个事实是本文用到的唯一产品声明。它们落在白名单内的非敏感切片上的叶子三和叶子四。它们不会把叶子一的 payload 移入叶子三。
实战示例。一个公开 issue 包含一个 12 行的复现步骤和一个失败的 pytest。你要粘贴的一切都已经是公开的。
# repro_public.py
def clamp(n, lo, hi):
if lo > hi:
raise ValueError("empty range")
return min(max(n, lo), hi)
# test_clamp.py
import pytest
from repro_public import clamp
def test_inverted_bounds_raise():
with pytest.raises(ValueError):
clamp(0, 10, -10)
发送复现步骤。在远程盒子上运行测试。保持与人类贡献者相同的证据标准:patch 必须在之前失败、之后通过,且不能使用 Agent 悄悄弱化的测试。
pytest -q test_clamp.py
# patch 之后
pytest -q test_clamp.py
git diff test_clamp.py # 拒绝删除断言
公开不等于"跳过审查"。只是出口风险不再是阻塞因素。
将其标记为未执行的示例,直到你对属于自己的树运行它。它编码了这棵树,但不替代法律审查。
#!/usr/bin/env python3
"""payload_class.py — classify a workspace before a remote agent session."""
from __future__ import annotations
import argparse
import re
from pathlib import Path
SECRET_RES = [
re.compile(r"-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----"),
re.compile(r"AKIA[0-9A-Z]{16}"),
re.compile(r"sk_(?:live|test)_[A-Za-z0-9]+"),
re.compile(r"(?i)(password|secret|api[_-]?key)\s*[:=]\s*\S+"),
re.compile(r"(?i)postgres(?:ql)?://[^\s]+"),
]
RESTRICTED_RES = [
re.compile(r"(?i)\b(?:ssn|social security)\b"),
re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
re.compile(r"(?i)\b(?:patient|medical record|iban)\b"),
re.compile(r"(?i)\b(?:card_last4|cvv|pan)\b"),
]
SKIP_PARTS = {".git", "node_modules", "__pycache__", ".venv"}
def iter_files(root: Path):
for p in root.rglob("*"):
if not p.is_file():
continue
if any(part in SKIP_PARTS for part in p.parts):
continue
if p.stat().st_size > 1_000_000:
continue
yield p
def classify(root: Path) -> str:
secret_hits = []
restricted_hits = []
for p in iter_files(root):
try:
text = p.read_text(encoding="utf-8", errors="ignore")
except OSError:
continue
for rx in SECRET_RES:
if rx.search(text):
secret_hits.append(str(p))
break
for rx in RESTRICTED_RES:
if rx.search(text):
restricted_hits.append(str(p))
break
if secret_hits:
return "secret_bearing"
if restricted_hits:
return "restricted_record"
return "internal_or_public"
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", type=Path, default=Path("."))
parser.add_argument(
"--allow",
choices=[
"secret_bearing",
"restricted_record",
"internal_nonsensitive",
"public",
],
required=True,
help="The leaf you believe you are on.",
)
args = parser.parse_args()
found = classify(args.root)
print(f"found={found} claimed={args.allow}")
if args.allow in {"public", "internal_nonsensitive"} and found != "internal_or_public":
return 2
if args.allow == "restricted_record" and found == "secret_bearing":
return 2
if found == "secret_bearing" and args.allow != "secret_bearing":
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())
分类器的最小测试:
# test_payload_class.py
from pathlib import Path
import payload_class
def test_secret_env(tmp_path: Path):
(tmp_path / ".env").write_text("PASSWORD=EXAMPLE_ONLY\n")
assert payload_class.classify(tmp_path) == "secret_bearing"
def test_restricted_ticket(tmp_path: Path):
(tmp_path / "ticket.txt").write_text("patient iban listed in notes\n")
assert payload_class.classify(tmp_path) == "restricted_record"
def test_clean_src(tmp_path: Path):
(tmp_path / "clamp.py").write_text("def clamp(n, lo, hi):\n return n\n")
assert payload_class.classify(tmp_path) == "internal_or_public"
pytest -q test_payload_class.py
python3 payload_class.py --root . --allow internal_nonsensitive
echo $?
如果退出码是 2,不要启动远程会话。先修复 payload 分类。
这张表是树的扁平化版本。如果某行需要一个你尚未掌握的事实——供应商日志保留、数据策略范围、替身是否真的失败——那你还没有出口决策。
这棵树无法检测图片、编译后的二进制文件,或 Agent 可能仍会用 cat 读取的加密 vault 文件中的秘密。正则表达式会漏掉自定义 token 格式。还会在讨论"password"这个词的文档上产生假阳性。
谁不应该使用这种方法:
受 HIPAA、PCI 或同等管控约束、需要有经验证的 DLP 产品而非 gist 量级脚本的团队。
远程供应商保留条款未知的任何人;叶子三假设你可以接受供应商日志。
需要生产数据才能复现 bug 的会话。如果替身无法让测试失败,免费远程循环就是错误的工具。
分类器是一个 pre-prompt 关卡。它不是 patch 的神谕,也不是 git diff 审查的替代品。
关于模型是否比开发者"写得更好"的争论,跳过了一个更先决的问题:你发送了什么?免费模型加免费服务器不会把叶子一或叶子二变成叶子三和叶子四。它只是让叶子三和叶子四执行起来更便宜。
运行分类器。把叶子编号说出来。然后启动 Agent——或者不启动。