Repository navigation
[17.0-rc2验收] 安全:REST /actions 与 /automation 派发路由缺少匿名拒绝门 —— 未认证调用者可触发 system 提权的 RLS/FLS 绕过写入 #5519
Copy link
Copy link
Closed
Labels
bugSomething isn't workingSomething isn't workingdomain:clipm:dispatchedpm:queuepriority:p0Critical: blocker, must ship before MVPCritical: blocker, must ship before MVP
Description
Activity
- addedpriority:p0Critical: blocker, must ship before MVPCritical: blocker, must ship before MVP
on Aug 5, 2026 分诊:
pm:queue+domain:cli+priority:p0。- 落点锚定:修复落在
packages/runtime/src/domains/actions.ts(及 automation 派发面)进派发前加shouldDenyAnonymous前置门,与/data、/meta、/ai、/security对齐同一基线 ——packages/runtime按域表归domain:cli。 - 过时前提检查:origin/main 现查
actions.ts无shouldDenyAnonymous,缺口仍在;该文件近日合并(fix(runtime):ctx.user.name交付真实 display name —— 三条 dispatch 路径统一 user 形状 (#5372) #5518,ctx.user.name 显示名)与本缺口无关。三仓检索无在飞 PR 覆盖。 - 判级 p0:未认证调用者可触发
isSystem: true的 RLS/FLS 绕过写入并已实测落库,且与 把 public 从"全局开关的副产品"升级为声明式能力,然后删掉 api.requireAuth 开关 #3963 墓碑「匿名访问一律拒绝」的平台承诺直接矛盾 —— 属安全基线缺口,非行为偏好。 - 查重:Security: MCP action surface must gate on
ai.exposed— action bodies run trusted (unbounded RLS/FLS), so invoke-time is the only agent boundary (#2849) #2849(MCP 面 invoke-time 门)、Security: AI ToolExecutionContext contract documentssystem-level as the missing-actor default — a contract-level fall-open across all data tools #2991(closed,AI tool 上下文 fall-open)为同一安全模型的不同传输面,正文区分成立。
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 落点锚定:修复落在
认领(PM 会话
session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道,p0 + v17-rc2 优先级即时派发)- 分支:
claude/issue-5519-actions-anonymous-gate - 工作树:
../objectstack-5519(os-dev 自建) - 文件面:
packages/runtime/src/domains/actions.ts(匿名门主落点)、automation 派发同族域文件、packages/runtime/src/security/(仅复用shouldDenyAnonymous,不改其语义)+ 新增测试。不触碰packages/rest/**(fix(rest): 缺表错误只在「缺的正是所请求对象」时才判为 unknown object (#5462) #5530 在飞,rest-server.ts 面隔离)。 - 方向锚定分诊席 15:54Z 判读:进派发前加
shouldDenyAnonymous前置 401 门,与/data、/meta、/ai、/security同一基线;requiredPermissions/ai.exposed细粒度授权在匿名门之后原样保留。
Generated by Claude Code
- 分支:
开工跟进(os-dev,承接 PM 会话
session_016FNvXhtSdnEGEfLEsMmvxh的派发,见上条认领评论)- 分支:
claude/issue-5519-actions-anonymous-gate - 工作树:
../objectstack-5519(基于当前origin/main) - 计划:①先在我动手时刻的
origin/main上复核前提(harness 集成测试复现「匿名 → 200」现状)②在handleActionsRequest与 automation 派发进派发前加shouldDenyAnonymous401 前置门 ③测绘复用同一派发入口的内部/系统路径,逐条钉住不回归 ④反向验证(先判方向后实跑)。 - 文件面自限:仅
packages/runtime/**;不触碰packages/rest/**(fix(rest): 缺表错误只在「缺的正是所请求对象」时才判为 unknown object (#5462) #5530 在飞)与packages/spec/**;shouldDenyAnonymous本体语义不改,只复用。
Generated by Claude Code
- 分支:
ACCEPT → PR #5569(PM 会话
session_016FNvXhtSdnEGEfLEsMmvxh,cli 车道,p0 闭环)验收依据:
- 前提复核比原单更广且全部真机取证:除 script action 提权写外,匿名还能触发 flow(真实 runId)、拉全量 flow 清单、DELETE 注销已注册 flow(破坏性写,原单未记录);同进程
/data401 对照成立。 - 门的落点与信封与
/ai、/meta、/security三处现有基线逐字一致;requiredPermissions/ai.exposed/ ADR-0104 契约语义原样排在门后。 - 内部路径按调用者枚举测绘(非读注释):MCP
run_action、声明式 endpoint 执行器(自带authRequired门)、引擎内部触发均不经过这两个 HTTP seam;isSystem照常通过——「内部/系统派发不回归」有 socket 级用例钉住。 - 反向验证方向先判后跑,handler 级 12 红 / socket 级 7 红全部收到修复前答案;object-less 用例的「断言因空而绿」(fix(lint): 收敛 validate-expressions / validate-security-posture 的 spec 不声明键
??别名读法 (#5017) #5046 形状)被自查抓出并按 fixture 三分法 replace wholesale——这类自我纠错正是三分法要的产出。52 例既有用例逐条三分,无批量重拼;基线单独确认无既有红。 - 消费半径:runtime 1397 / dogfood 506 / cli 807 全绿,changeset 含升级须知。
Open question 采纳 A:本 PR 按 p0 窗口只在 runtime 内交付防线(真 socket 用例已守住),proof 工件补齐已单独立单 #5570;偶发 400 HookConditionError 观察已按「复现单」形状立 #5571(机制未确认,标题只陈述症状)。两单均无标签待分诊。
CI 核后转 ready 挂 auto-merge,跟到 MERGED。落地后解锁 #5138(同
action-execution.ts面串行)。
Generated by Claude Code
- 前提复核比原单更广且全部真机取证:除 script action 提权写外,匿名还能触发 flow(真实 runId)、拉全量 flow 清单、DELETE 注销已注册 flow(破坏性写,原单未记录);同进程
- added 3 commits that reference this issue
on Aug 17, 2026 - added a commit that references this issue
on Sep 28, 2026
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingdomain:clipm:dispatchedpm:queuepriority:p0Critical: blocker, must ship before MVPCritical: blocker, must ship before MVP
环境行:
hotcrm@0899b4f + @objectstack 17.0.0-rc.2(平台源码/home/user/objectstack,dev server:objectstack dev,已装@objectstack/plugin-auth,登录/鉴权正常)现象(p0 越权 / 未认证写入)
同一个进程里,
@objectstack/rest注册的数据面路由(/data、/meta)对匿名请求一律 401UNAUTHENTICATED;但@objectstack/runtime的 dispatcher-plugin 注册的/actions/*和/automation/*路由没有任何匿名拒绝门,匿名调用者可以:isSystem: true执行(buildActionExecutionContext),即 RLS/FLS 绕过 的系统提权写入;POST /automation/:name/trigger→ 启动 flow run。action 的唯一前置门是 ADR-0066 D4 的
actionPermissionError,它在 action 未声明requiredPermissions时对所有调用者(含匿名)放行。绝大多数 action 默认就是未声明的(HotCRM 全部 13 个 action 无一声明requiredPermissions),因此整条 CRM action 写入面对匿名开放。这与平台自己的承诺矛盾。
packages/spec/src/stack.zod.ts的api.requireAuth退役墓碑(#3963)明确写道:而 action body 做的正是对象写入(比
/data写更强,因为它系统提权绕过 RLS/FLS),却绕过了这条"永远拒绝匿名"的保证。复现(可直接粘贴执行,HOST=起的 dev server)
复现两次均一致(send_email 匿名连打两次各生成一条
sys_email,sent_by:'system',已用 admin 读回确认持久化;raw sqlite 亦可见)。期望 vs 实际
POST /api/v1/actions/...(和/automation/...)在派发之前即被 401 拒绝,与/data、/meta同一基线;requiredPermissions/ai.exposed等更细的授权在通过匿名门之后再判。isSystem:true系统提权执行并绕过 RLS/FLS 落库;automation trigger 直接启动 run。落点分析(读到的源码位置)
门缺失点:
packages/runtime/src/domains/actions.tshandleActionsRequest全程无匿名判定 —— 唯一前置门是actionExec.actionPermissionError(...)(约 line 217)。而packages/runtime/src/action-execution.ts的actionPermissionError(line 300+):对比:
domains/ai.ts:127、domains/meta.ts:59、domains/security.ts:92都在处理前调用shouldDenyAnonymous({ userId, isSystem })。domains/actions.ts没有这一步。路由注册点:
packages/runtime/src/dispatcher-plugin.ts的registerActionRoutes(distindex.js约 line 8108)把/actions//:action、/actions/:object/:action、/actions/:object/:action/:recordId直接接到dispatcher.dispatch("POST", ...),未经过mountRouteOnServer的route.auth401 门,也没有 rest-server 的enforceAuth。/automation/*同样如此。相较之下@objectstack/restrest-server.ts的每个/data、/metahandler 都先if (this.enforceAuth(req, res, context)) return;(内部shouldDenyAnonymous)。同一进程两套注册路径,只有 rest 那套设了门。提权确认:
packages/runtime/src/security/resolve-execution-context.ts文档保证 "Anonymous requests yield{ isSystem: false, positions: [], permissions: [] }" —— 匿名本身不是 system;但 action body 一旦进入,buildActionExecutionContext强制isSystem:true(dist line ~1698),所以匿名触发即拿到系统提权、RLS/FLS 绕过的 body 执行上下文。影响面与关联
requiredPermissions,故全部可被匿名调用。按验收纪律不在 hotcrm 重复立单,此处记录关联。ai.exposed— action bodies run trusted (unbounded RLS/FLS), so invoke-time is the only agent boundary (#2849) #2849 处理的是 MCP action 面按ai.exposed的 invoke-time 门,与本单是同一"body 系统提权、invoke-time 才是边界"的安全模型,但覆盖的是不同的传输面(MCP);本单是 REST/actions+/automation缺少更底层的匿名基线门。Security: AI ToolExecutionContext contract documentssystem-level as the missing-actor default — a contract-level fall-open across all data tools #2991(已闭)是 AI tool 上下文system默认 actor 的 fall-open,亦不同面。两仓 open/closed 检索未见覆盖 REST /actions 匿名调用的现单。建议方向(仅供参考,不在本单实现)
在
domains/actions.ts(及 automation 派发)进入派发前加shouldDenyAnonymous({ userId: ec?.userId, isSystem: ec?.isSystem })→ 401,与/data、/meta、/ai、/security保持同一基线;之后再走requiredPermissions/ai.exposed。