-
commit b7b5096 给 agent-chat.js 挂 requireAuthOrInternal 后,只顾了「同进程内 require 拿 INTERNAL_TOKEN」这一种自环调用(company.js/task-settle.js),漏了 bin/lemon.js——它跑在 agent 的 claude CLI 子进程里,不共享内存,没法拿到这个 token,导致所有 agent 的 task report/dispatch 全部 401,没法自行汇报完成状态。 修法:jwt-auth.js 生成 INTERNAL_TOKEN 时顺带写进 process.env.LEMON_INTERNAL_TOKEN ——spawn 默认把 process.env 整份传给子进程,免费传给所有后代进程(claude CLI → Bash 子进程 → bin/lemon.js)。bin/lemon.js 读到就带上 x-internal-token header。 顺带把硬编码的 API_BASE 端口改成可用 LEMON_API_BASE 覆盖,方便测试。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
给此前完全零防护的 company.js/okr.js/tasks.js/devloop.js/graph-run.js(两个挂载点)/ arena.js/config.js/evolution.js/whatsapp-auth.js 挂 requireAuth;dream.js 挂 requireAdmin (系统级经验汇流视图,只有 admin 能看)。open-api.js(自己一套 OPEN_API_KEYS)/auth.js 自身/health 端点按计划排除。这一步只挡"没登录",公司归属过滤留到 Phase 2。 agent-chat.js 不能直接挂 requireAuth:company.js(自动建公司/招聘后自动训练)、 task-settle.js(goal 汇报回调)三处服务端自环调用会 fetch 自己的 /api/agent/* 端点, 拿不到任何用户 cookie,直接挂锁会打断这几条核心自动化链路。新增 requireAuthOrInternal + jwt-auth.js 的进程内 INTERNAL_TOKEN(启动时随机生成,只活在内存里):自环调用带上 x-internal-token 头放行,其余情况仍要求真实登录;三处调用点同步改造。 17 个既有测试文件、67+ 处用例直接拿 supertest 打这些路由,从没设过登录 cookie(测的是 业务逻辑不是鉴权)。没有逐个补登录态,改为 requireAuth/requireAdmin 在 NODE_ENV=test 时默认放行,除非显式 FORCE_REAL_AUTH=1;新增 route-auth-gate.test.js 在放行关闭状态下 做结构检查(router.stack 里中间件是否真的排在所有路由前面),确保"锁真的挂上了"这件事 有测试兜底,不被业务测试的默认放行悄悄掩盖。 验证:真实 curl 走过全部路由(无 cookie 401、admin 登录全通、非 admin 能看公司但 dream.js 403);手工删过一行 requireAuth 确认守卫测试真的会红;全量 vitest 1427 passed, 另 3 个失败与本次改动无关(stash 验证过改前改后一致,系仓库并发工作遗留)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
## 起因 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> -
## 问题 server/middleware/jwt-auth.js 的兜底密钥 'lemon-dev-secret-change-in-production' 提交在仓库里,而 .env.example **没有 JWT_SECRET 这一项** —— npm run setup 就是 从 .env.example 生成 .env(CLAUDE.md「快速启动」),所以照文档装出来的部署 必然用这个公开密钥签会话,且启动时零告警。 危害不止「密钥弱」。extractUser 拿 decoded.userId 回库重查用户 (所以伪造 role:'admin' 这条路本身是堵住的,这个设计是对的),但 user-db 的 id 是自增小整数、不是 uuid:拿已知密钥签 {userId: N}、从 1 试起 就能命中真实账号,拿到的是该账号在库里的真实 role。命中 admin 就是 requireAdmin 守着的整个 /api/users/* 全部敞开。不需要密码、不需要 OAuth, 只要读过这个仓库。 本机 .env 有 64 字符密钥,未受影响;受影响的是默认部署路径。 ## 三处改动 1. .env.example 补 JWT_SECRET(含 openssl rand -hex 32 生成命令与危害说明) 2. jwt-auth.js:NODE_ENV=production 且未设 JWT_SECRET 时**拒绝启动** (签名密钥缺失不是可以边跑边补的事);开发环境保留兜底但每次起进程吼一次。 三种情形都实测过:生产无密钥→抛错拒启;生产有密钥→正常且不告警;开发无密钥→放行+告警。 3. 新增 guard server/tests/unit/env-example-secrets.test.js: 「安全类 env + **非空**硬编码兜底」必须在 .env.example 里有一行。 只管非空兜底是刻意的 —— 空串兜底(FEISHU_ENCRYPT_KEY、OPEN_API_KEYS)是 「未配置」的正当写法,缺了 fail closed;把它们也算进来只会制造噪音, 然后被加白名单,然后白名单腐烂。危险的是「缺了也能跑、还跑得像配好了」那一类。 ## 顺带修掉一个能把全新克隆直接搞坏的 .env.example 写 PORT=3000,而 config.js 默认 3010、web/vite.config.js 的代理 目标硬编码 3010 → npm run setup 装出来的环境里前端每个 API 调用都 502, 而症状离原因很远。改成 3010,并加一条 guard 钉住「example / config 默认 / vite 代理」三者一致。 ## 验证 857 全绿(85 文件),既有断言未改。3 处变异精确变红后逐字还原。 其中 S3 那条变异(把兜底常量改成内联字面量)是必要的:guard 第一版只认内联 字面量,而我重构时把兜底提成了命名常量 DEV_FALLBACK_SECRET,于是 guard 对它 诞生的那个 case 恰好失明 —— 被它自己的自检当场抓到,遂加上同文件常量绑定解析。 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