-
## 起因 integration 测试里 `process.env.API_KEYS = 'test-api-key-123'` 是死代码: 生产读的是 OPEN_API_KEYS(config.js:31),而这个测试文件里 API_KEYS 只出现 这一次赋值,没有任何 /api/open/* 请求、没有任何 key 相关断言。已删。 (更正上一轮的判断:它不是「为了错误的原因而绿」——它什么都没测, 所以也谈不上错。两个缺陷互相抵消成了无害。) ## 为什么错名字能活这么久 api-key-auth.test.js 原有 7 条用例**全在测 extractApiKey**(怎么从 header/query 取出字符串),而做鉴权决定的 requireApiKey 一条都没有。OPEN_API_KEYS 那条分支 从来没被执行过 —— 没有测试执行过的路径,喂给它什么名字都不会有人知道。 补 4 条(拦/放两侧各两条):无 key → 401「需要 key」/ key 无效 → 401「key 不对」/ 命中系统 key 列表 → 放行且挂 req.apiKey / 命中用户 api_key → 放行且挂 req.user。 ## 补完当场抓到一个真缺陷(这一条才是重点) requireApiKey 把 findByApiKey 返回的**原始数据库行**挂上 req.user,带着 password_hash。JWT 那条路(jwt-auth.extractUser)一直是过 toPublicUser 才挂的 —— 两条鉴权路径行为不一致,而 req.user 是 handler 唯一的身份来源。 当前没有实际泄漏(open-api 的 handler 只读 req.user?.id / ?.email, 没有哪个把整个对象回给调用方),但将来任何一个 res.json({ user: req.user }) 都会把 bcrypt 哈希发给外部 API 调用方。改成过 toPublicUser。 顺带确认 requireApiKey 本身是 fail-closed 的:无 key 401;key 既不匹配用户 api_key 也不在配置列表 401。OPEN_API_KEYS 为空只是让系统级 key 全部失效, 不放开任何东西 —— 所以那个错名字不构成安全漏洞。 ## 验证 862 全绿(85 文件)。4 处变异逐一精确变红后逐字还原。 其中 T3 值得记:第一版「无 key → 401」只断言状态码,而把 if (!key) 改成 if (false) 之后测试仍全绿 —— 因为纵深防御,key=null 会落到第二道门照样 401。 两者的区别在**文案**(「需要 key」vs「key 不对」),而我在另一条用例的注释里 自己写了「两种失败要分得开」却没断言它。补上文案断言后 T3 才真的变红。 只断言状态码的鉴权测试基本都有这个盲区。 数据核查:跑完全套后共享 users.sqlite 的 sha256 不变、用户数仍为 1 (新用例用 USERS_DB_PATH 打到 /tmp —— 这个覆盖口是上一轮刚补的, 在它存在之前这组用例会往共享生产库里插测试用户)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
起因是一个看起来无害的问题:<repo>/data/users.sqlite 为什么是 0 字节。 答案是没人用它——user-db 走三级上跳落在仓库外的 ~lemonAI/data(跨兄弟仓库 共享的平台数据,与 skill-db / benchmark-db 一致;仓库内 data/ 放 graph/companies/ devloop 这类租户数据)。那个空文件是遗留,已删。 顺着查下去发现三处同型缺陷,形态都是「产生 → 传递 → 落库 → 读回」漏掉读回, 而且是最有欺骗性的一种:测试设了隔离用的 env,没有人读,于是数据全写进 **共享的生产库**,测试照样全绿,afterAll 删的 /tmp 文件从来没被创建过、清理还“成功”。 1) USERS_DB_PATH 压根不存在 user-db.test.js 只能直写共享用户库(注释老实写着 "uses hardcoded data/users.sqlite"),afterAll 是空的、拿「邮箱按 UUID 唯一所以不会撞」 当成了不用清理的理由。不撞是真的,不堆积是假的:357 个用户里 356 个是夹具。 加 USERS_DB_PATH 覆盖口(口径同 graph-db 的 GRAPH_DB_PATH),测试改为 先设 env 再清 require.cache 再 require(DB_PATH 是加载时求值的常量, 顺序错了照样静默失效),afterAll 真删库文件。 2) BENCHMARK_DB_PATH 有人设、没人读 benchmark-db.test.js:13 与 integration/api-routes.test.js:43 都在设它, 而 benchmark-db.js 那一行不读 → 共享 benchmark 库攒了 342 行 skill_name=benchmark_test。补上读取后,两个测试文件一行不改就真隔离了 (日志里 Initialized SQLite at /tmp/... 第一次是真的)。 3) SKILLS_DIR 是写错的变量名 config.js:13 读的是 SKILLS_CODE_DIR,所以 skillsCodeDir 一直指向那个 2.7 万条的真实共享 skills 目录。两个测试文件的变量名改正。 新增 guard server/tests/unit/test-isolation-env.test.js: 测试赋值过的**路径类** env(_DIR/_PATH/_ROOT 结尾)必须有生产读者。 只管路径类是刻意的——那是会造成静默数据污染的地方,而 HOME(os.homedir 间接读)、 JWT_SECRET、PORT 这类天然不在范围内,不需要维护会腐烂的白名单。 顺带钉住「共享 data 在仓库外」这个有意的分层,免得被“修”回仓库内。 guard 第一版把自己文档注释里的举例当成真赋值、自己判红了,所以两侧都先剥注释—— 凡是静态扫源码的检查都要先想清楚该量的是代码还是文本。 数据清理:共享 ~lemonAI/data/users.sqlite 删掉 358 行夹具 (不是 356——修好之前又跑了两轮测试,每轮 +2,正好印证泄漏一直活跃), 删前备份 + 计数闭合校验(保留行 + 匹配行 == 总数才动手), 删后真人 bboonnee@gmail.com 完好,总数 1。 验证:854 全绿(84 文件)。跑完全套后 users / benchmark_logs / skills 三个共享库的 sha256 逐位不变——修好之前这不可能。 3 处变异逐一精确变红(去掉任一 env 读取、把变量名改回错的)后逐字还原。 未动:JWT_SECRET / API_KEYS 生产侧无读者或名字不符(config.js 读 OPEN_API_KEYS), 不造成数据污染,另报;共享 benchmark 库那 342 行夹具未清,等确认。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
- Agent as Micro-Container: each agent lives in data/agents/{id}/ with full encapsulation - Persona vs Capability split: agent.md (identity) + .claude/skills/main_skill/SKILL.md (workflow) - Conversations co-located: data/agents/{id}/conversations/ replaces global dir - Meta Skills renamed: meta_hunt, meta_digest, meta_creator, meta_evolving, meta_benchmark, meta_research - Dynamic Hot-Reload: AgentManager.getAgentConfig() falls back to filesystem scan - Parallel Sub-Agent orchestration via Promise.all with multi-terminal UI - Removed SQLite dependency, global skills/ directory, and legacy conversations/ - Full architecture docs: 18-local-filesystem-first-agent-encapsulation.md