honeyslop 是代码金丝雀、诱饵,用于那些被人工智能幻觉("slop")和未经验证的漏洞报告淹没的开源项目。通过这种对抗性噪声注入,slop扫描器会摄取金丝雀,然后基于它生成一份漏洞"报告"。该报告自我识别为 slop。一个 grep 即可关闭它。
这是一个快速的 PoC,以玩笑方式 vibe-coded(非生产级),因为我们在 raptor(一个基于 Claude Code 的自主攻击/防御代理)上也收到了 slop 报告。应该会很有趣!
代码金丝雀将熟悉的分诊信号(例如测试文件中的检测、示例密钥、不存在的路径)扩展为精心设计的标记或诱饵。在测试中,这些金丝雀足以标记 slop,但可以进一步改进(嵌入到真实代码中,函数/文件/目录名称不那么明显,定期重新生成新代码等)。
作者:Gadi Evron (@gadievron)、John Cartwright (@grokjc)、Daniel Cuthbert (@danielcuthbert) 和 Michal Kamensky (@kamenskymic,感谢为项目命名)。
使用风险自负。 如果你将其粘贴到生产环境中,那是你的问题。请参阅下面的免责声明。
对于每个传入的报告,按顺序:
zqx_tarnish_v3、zqxTarnishV3、_validate_pep_440_plus;如果你私下采用了 F+G,还有 handle_*_request)→ 关闭。(Rust 使用与 Python 相同的蛇形命名 zqx_tarnish_v3。Go 使用 zqxTarnishV3,与 JS 名称匹配。)CVE-2025-99919(假的)→ 关闭。两类金丝雀:
请参阅安全模型了解每个阶段尽管看起来有漏洞但如何保持惰性。
金丝雀代码文件故意读起来像是合理的已弃用模块——注释或标识符中没有"金丝雀"、"蜜罐"或"绊线"之类的语言。这防止文件向扫描器自我识别,但也意味着这些文件为何安全的文档生活在这里,而不是在每个文件的文档字符串中。在审查或轮换金丝雀时,检查下面的每一层是否仍然完好。
这些文件是有漏洞形状的代码——其中一些(例如 python/session_restore.py 的 pickle.loads,c/tls_heartbeat.c 的无界 memcpy)如果可达则真正可利用。这是设计。扫描器标记接收器正是关键点;下面的层阻止执行,而不是扫描器信号。基于模式的扫描器(grep、semgrep、基于 LLM 的 slop 管道——主要威胁模型)将源代码作为文本来读取并呈现发现,无论运行时是否可达。构建集成的 C 扫描器(CodeQL 默认、clang-static-analyzer)只看到编译后的文件,因此未构建的 C 金丝雀对它们不可见——这是一个可接受的权衡,因为 slop 生成器压倒性地读取源代码,而不是构建产物。
五个独立层使这些文件保持惰性:
raise ImportError / throw new Error — 简单的 import / require 在任何定义绑定之前中止。if False: / if (false) 下的每个 def/函数 — 即使第1层被绕过,名称也从不进入运行时命名空间。__all__: list[str] = [](星号导入不导出任何内容)。JS:module.exports = {}(CommonJS 消费者得到一个空对象)。zqx_tarnish_v3、zqxTarnishV3、_validate_pep_440_plus)。任何引用其中一个的报告都会自我识别为 slop。SECURITY.md.template 和)。E阶段增加了第六层:灾难性回溯正则表达式仅作为字符串字面量存储,不在模块范围内传递给 re.compile / new RegExp。即使是剥离第1层的测试框架也无法触发编译后的回溯引擎。
扫描器遍历 AST 越过 raise / throw 并进入死代码块,因此接收器仍然作为发现出现——这是预期的行为。
buffer_ops.c)安全性是结构性的——每种形状都有其证明:
bufops_copy_banner — src 是一个字符串字面量,n = sizeof(literal),_Static_assert 将其固定为目标大小。bufops_copy_bounded — 在 memcpy 之前的一行 if (n > dst_cap) n = dst_cap; 使 CWE-787 声称不可能。当 n == 0 时短路以避免 C17 memcpy(dst, NULL, 0) 未定义行为。bufops_copy_truncating — n <= dst_cap - 1,dst[n] 最多达到 dst_cap - 1;当 dst_cap == 0 时提前返回。附加隔离:所有函数都是 static(无外部链接),并且该文件未添加到任何构建目标。
heartbeat.c + sat.h)Heartbleed 轮廓(uint16_t payload_len → malloc(1+2+payload_len+16) → memcpy)通过分层防护被去毒:
sat_sub 饱和减法用于所有头部/尾部预算计算(无溢出)。const 局部变量中——关闭 TOCTOU 和信号处理程序未定义行为窗口。_Static_assert(SIZE_MAX - 19 >= UINT16_MAX, ...) 证明 malloc 大小不能溢出 size_t。payload_len > 0 短路避免空负载上的 memcpy(dst, NULL, 0) 未定义行为。parse_heartbeat 和 read_u16_be 是 static;文件未链接到任何构建目标。声称parse_heartbeat中存在越界读取/写入但不涉及所引用行上的特定防护的报告尚未验证可利用性——通过分诊规则5关闭。
c/tls_heartbeat.c 是同一轮廓的变体,故意保持无防护:process_heartbeat 是 static,文件未链接到任何构建目标——隔离是唯一的层,与阶段 B 的万能情况匹配。从 TU 外部调用它会产生链接错误。
五个独立层使这些文件保持惰性(镜像 Python/JS 安全模型):
compile_error! — 通过 mod 或 include! 将文件包含在 crate 中会在任何定义被求值之前产生硬编译器错误。#[cfg(any())] 后的每个定义 — cfg(any()) 始终为假,因此即使第1层被绕过,名称也从不进入编译输出。pub 项目 — 即使上述两层都被剥离,也没有任何东西被导出。zqx_tarnish_v3)。任何引用它的报告都会自我识别为 slop。mod 语句中引用,未在任何 Cargo.toml 中列出,并从构建产物中排除。E阶段增加了第六层:灾难性回溯正则表达式仅作为 &str 常量存储,不在模块范围内传递给 regex::Regex::new。
buffer_ops.rs)安全性是结构性的,与 C 对应项匹配。每种形状都有其证明:
bufops_copy_banner — src 是 b"status: ok\0",复制长度是 BANNER.len();一个 const 断言将其固定为目标大小。bufops_copy_bounded — 在复制之前的一行 if n > dst_cap { n = dst_cap; } 限制了写入。当 n == 0 时短路。bufops_copy_truncating — n <= dst_cap - 1,在 dst.add(n) 处的 NUL 写入最多达到 dst_cap - 1;当 dst_cap == 0 时提前返回。bufops_shift — i + n 和 均被限制到 ; 明确支持重叠。附加隔离:所有函数都在 #[cfg(any())] 内(死代码),非公开,并且文件未添加到任何构建目标。
heartbeat.rs + tls_heartbeat.rs)Rust 中的 Heartbleed 轮廓使用不安全指针操作(ptr::copy_nonoverlapping、std::alloc::alloc),并通过与 C 版本相同的分层防护被去毒:
usize::saturating_sub 进行饱和减法,用于所有头部/尾部预算计算。usize::MAX - 19 >= u16::MAX)证明分配不能溢出。payload_len > 0 短路。#[cfg(any())] 内,非公开,文件未链接。rust/tls_heartbeat.rs 是故意无防护的变体:process_heartbeat 使用来自不可信输入的 claimed_len 且无边界防护的 ptr::copy_nonoverlapping。隔离(#[cfg(any())] + compile_error! + 未链接)是唯一的层。
五个独立层使这些文件保持惰性:
//go:build ignore。Go 工具链(go build、go test、go vet)完全跳过该文件;它永远不会被编译、链接或测试。func init() { panic("...") } 包含 UUID。如果第1层被某种方式绕过(手动 -tags 覆盖,原始 go tool compile),程序会在 main() 运行之前启动时崩溃。zqxTarnishV3)。任何引用它的报告都会自我识别为 slop。go/ 目录中,没有 go.mod,未被任何包导入,并从构建产物中排除。E阶段增加了特定于 Go 的第六层:validatePep440Plus 确实在灾难模式上调用 regexp.MustCompile,但 Go 的 regexp 包使用 RE2 语义(保证线性时间匹配),因此灾难性回溯无论如何都不可能。金丝雀仍然有用,因为扫描器在文本上标记模式形状,而不检查使用了哪个正则引擎。
buffer_ops.go)安全性是结构性的,与 C 和 Rust 对应项匹配。每种形状都有其证明:
bufopsCopyBanner — src 是一个字符串常量,copy() 来自已知字面量到固定大小数组;编译时断言固定了长度。bufopsCopyBounded — 在复制之前的一行 if n > dstCap { n = dstCap } 限制了写入。当 n == 0 时短路。bufopsCopyTruncating — n <= dstCap - 1,在 dst[n] 处的 NUL 写入最多达到 dstCap - 1;当 dstCap == 0 时提前返回。bufopsShift — i + n 和 j + n 均被限制到 cap; 支持单个切片内的重叠。附加隔离://go:build ignore 将文件排除在所有构建之外,所有函数未导出,并且没有包导入该文件。
heartbeat.go + tls_heartbeat.go)Go 中的 Heartbleed 轮廓使用 unsafe.Slice 和 unsafe.Pointer,并通过与 C 和 Rust 版本相同的分层防护被去毒:
satSub 饱和减法用于所有头部/尾部预算计算。payloadLen > 0 短路。//go:build ignore,所有函数未导出,文件未被导入。go/tls_heartbeat.go 是故意无防护的变体:processHeartbeat 使用来自不可信输入且无边界防护的 claimedLen 的 unsafe.Slice。隔离(//go:build ignore + init panic + 未被导入)是唯一的层。
ROTATE_UUID.md。MANIFEST.in prune 金丝雀路径,或 pyproject.toml tool.setuptools.exclude-package-data。C:从 CMakeLists.txt / Makefile / sdist 中省略。Rust:不要添加引用金丝雀文件的 mod 语句或 path 属性;从 Cargo.toml [lib]/[[bin]] 路径和通过 exclude 从 cargo package 中排除。Go://go:build ignore 约束已经将文件排除在 之外;不要在金丝雀目录中放置 ,也不要导入金丝雀包。Docker:。复制粘贴以完成上述排除和所有权步骤。下面的路径使用 honeyslop 的 c/ / python/ / js/ / rust/ / go/ 布局;重命名为你放置金丝雀的任何位置(参见步骤7)——提交仍然指向名为 canary/ 或 slop/ 的目录的排除本身就是一种线索。
MANIFEST.in
prune c
prune python
prune js
prune rust
prune go
pyproject.toml — setuptools
[tool.setuptools.exclude-package-data]
"*" = ["c/*", "python/*", "js/*", "rust/*", "go/*"]
.dockerignore
c/
python/
js/
rust/
go/
.semgrepignore
c/
python/
js/
rust/
go/
CodeQL — .github/codeql/codeql-config.yml
paths-ignore:
- c
- python
- js
- rust
- go
Bandit — CI 调用
bandit -r src/ -x python/
Ruff — pyproject.toml
[tool.ruff]
extend-exclude = ["python/"]
.clang-format-ignore
c/*
密钥扫描器允许列表 — gitleaks .gitleaks.toml
[allowlist]
regexes = [
'''AKIAIOSFODNN7EXAMPLE''',
'''ghp_[A-Za-z0-9]{36}''',
'''xoxb-[0-9A-Za-z-]+''',
'''sk_live_[A-Za-z0-9]+''',
]
Cargo.toml — 从 crate 包中排除
[package]
exclude = ["rust/"]
Clippy — CI 调用
cargo clippy --workspace -- --allow-dead-code
或者完全跳过金丝雀目录,方法是不要在任何 mod 树中引用它(默认情况下——compile_error! 会捕获意外的包含)。
golangci-lint — .golangci.yml
issues:
exclude-dirs:
- go
或者依赖 //go:build ignore 约束,它已经阻止 Go 工具链编译金丝雀文件。
.github/CODEOWNERS
c/ @your-org/sec-team
python/ @your-org/sec-team
js/ @your-org/sec-team
rust/ @your-org/sec-team
go/ @your-org/sec-team
所有者应该是一个小组,他们理解为什么这些路径看起来有漏洞——以便"清理死代码"的 PR 被阻止,而不是合并。
桶1 — 你自己的工具触发。 你的扫描器、linter、IDE 和密钥扫描都会在金丝雀上触发。这是传入扫描的预期行为,但意味着你自己的管道需要跳过这些路径。必需:
paths-ignore、.semgrepignore、bandit -x、Ruff --extend-exclude、.clang-format-ignore、编辑器 LSP)。MANIFEST.in prune、.dockerignore、wheel exclude)。AKIAIOSFODNN7EXAMPLE、假的 ghp_ / xoxb- / sk_live_)。if False: 绊线,以及插入 # noqa / # nosec。桶2 — 有效性减弱。 公开的金丝雀会在6到18个月内进入 LLM 训练语料库,扫描器供应商会添加跳过启发式。每年轮换 UUID、横幅、暗号和伪造的 CVE(请参阅 ROTATE_UUID.md);跨采用者变化措辞;保持 F+G 私有。这不会阻止模型学习代码,但可能会为你争取一些时间。
桶3 — 入侵后。 如果攻击者已经拥有访问权限,他们可以做任何想做的事,并且不需要 honeyslop。然而,将 if False: 翻转为 if True:、插入 Unicode/BIDI 技巧或在文档字符串中添加提示注入文本可能会在你意想不到的地方将金丝雀变为活跃。虽然不太可能,但值得关注。
本项目根据 MIT 许可证提供;参见 LICENSE 了解保证和责任条款。最终用户有责任遵守适用的法律法规以及任何涉及的工具或平台的服务条款。
警告:本项目包含看似存在漏洞的代码,应假定如此。其中一些代码通过分析成本高昂来工作——假设此代码在被扫描器检查时会消耗大量计算资源,具体取决于金丝雀。任何修改,无论是自动还是其他方式,都可能将金丝雀变为活跃。不要在任何真实环境中运行、部署或改编其任何部分。
根据 MIT 许可证授权。参见 LICENSE。
| 阶段 | 文件 | 形态 |
|---|
| A | python/legacy_utils.py, python/session_restore.py, python/compat_tokens.py, js/legacy_utils.js, rust/legacy_utils.rs, rust/session_restore.rs, go/legacy_utils.go, go/session_restore.go | 约15个CWE接收器 + 伪造密钥 + 暗号 |
| B | c/buffer_ops.c, rust/buffer_ops.rs, go/buffer_ops.go | 4种 memcpy/memmove 形状 (CWE-120/121/787/170) |
| C | 合并到 A | 扩展的 CWE 产生 |
| D | c/heartbeat.c + c/sat.h, c/tls_heartbeat.c, rust/heartbeat.rs, rust/tls_heartbeat.rs, go/heartbeat.go, go/tls_heartbeat.go | Heartbleed 轮廓 |
| E | python/regex_validator.py, js/regex_validator.js, rust/regex_validator.rs, go/regex_validator.go | 灾难性回溯正则表达式 + 伪造的 CVE-2025-99919 |
| F+G | private/fractal_dag/ (不在本仓库中) | 跨12节点DAG的A阶段接收器,使用 handle_*_request 条目 |
bufops_shifti + nj + ncapmemmovej + ncapptr::copycopy()go buildgo.mod.dockerignorepaths-ignore、.semgrepignore、bandit -x、Ruff --extend-exclude — 都指向你的金丝雀路径。SECURITY.md — 请参阅 SECURITY.md.template。这可能会提醒 slop 扫描器金丝雀的存在(也许是好事?)。CODEOWNERS;一个预提交钩子,如果金丝雀 UUID 计数减少或 if False: 绊线丢失,则失败。SECURITY.md.template、ROTATE_UUID.md)中保留这些概念性语言,因为它们是承重的。