尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Sim 企业权限组条目端到端验证指南:从配置注册表到强制执行的七步审计

Sim 企业权限组条目端到端验证指南:从配置注册表到强制执行的七步审计 Sim 企业权限组条目端到端验证指南从配置注册表到强制执行的七步审计【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000 builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim本指南讲解如何在 Sim 开源仓库中对一个企业权限组Permission Group条目做端到端验证——从配置键PERMISSION_GROUP_FIELDS中的 key或能力CAPABILITY_RULES中的 capability出发逐一审计注册表条目、派生 Schema、管理端 UI、能力规则、实际强制点与测试最终证明管理员设置这个开关时到底有什么在拒绝请求以及我能否让拒绝真正发生。读完你将掌握一套可复用的七步审计清单、三条自动化审计脚本的判定逻辑以及一份可直接用于代码评审的报告模板。验证的出发点不问是否存在只问谁在拒绝这个技能的核心立场写在其开篇问题从来不是这个 key 是否存在于正确的位置——注册表registry已经用编译器把大部分结构性存在性问题解决了。真正的问题是如果组织管理员设置了这一项是什么在拒绝请求我能否亲手让那次拒绝发生文档用一个真实教训支撑了这个立场曾经有 12 个 key 只提供了复选框、提示文案hint和零服务端检查每一个都能通过纯结构审计。因此在找到那个throw之前不要假设任何东西被真正执行了。add-permission-group-item技能负责新增条目的流程与每一条不变量的设计理由而本技能是其配套的验证清单——文档称之为 the checklist。两者共享同一份 Read the system first 的起点列表。在开始验证前建议先阅读 .agents/skills/add-permission-group-item/SKILL.md。Step 1注册表条目apps/sim/lib/permission-groups/fields.ts第一步聚焦PERMISSION_GROUP_FIELDS中该条目的三个事实构建器builder、enforcement声明、以及声明顺序position。默认值是否默许default permissive三个构建器把默认值硬编码为false/null/[]见 fields.ts 中booleanRestriction的default: false、allowlist的default: null、denylist的default: []。因此真正的风险不是默认值本身而是一个名字把语义写反了——例如一个allowX布尔值。由于复选框渲染的是checked{!editingConfig[feature.configKey]}勾选 允许一个正向命名的布尔会被渲染成反向语义管理员勾选它以为在开启实际却在禁止。位置是否稳定position stable声明顺序就是线上顺序wire orderfields.ts顶部注释明确指出这里的声明顺序就是PermissionGroupConfig的 key 顺序、两个 zod schema 的顺序、以及每一个跨 API 边界的 config JSON 的顺序组编辑器用字符串化后的 config 做脏检查因此重排条目属于破坏性变更。fields.test.ts用一个 key 顺序契约测试把这个顺序钉死。如果git log -p显示某个 key 曾被移动过而非追加那说明它曾以编辑器脏检查回归的形式上线过。措辞是否准确phrasing accurateallowlist 的{ limited, empty }与 denylist 的字符串会被features.ts中的getActivePermissionGroupRestrictions读取见 features.ts并通过 Copilot 工作区 VFS 和企业平台上下文呈现给用户。因此必须确认empty表达的是一个都不允许No non-exempt integrations or blocks are allowed.而不是不受限制——同一个词在受限语境里被当成放开读取就是误导。从源码看allowlist 的empty文案确实在 fields.ts 写为 No non-exempt integrations or blocks are allowed.语义正确。hint 是否说真话the highest-value read这是本步骤中价值最高的一次阅读。一个capability类型的 key 在API 层拒绝请求见 Step 4/5因此如果它的 hint 写的是从侧边栏隐藏标签页/模块/导航项那是在对管理员撒谎——管理员以为自己只是在整理界面chrome实际上却在吊销整个模块。同一个字符串还会被复用作活动限制的说明文案在那里 hide 更是完全错误。任何残留在capabilitykey 上的 Hide the … hint 都是一个finding而非吹毛求疵label和category也要同样检查一个 Sidebar 或 Settings Tabs 分类在结构上就暗示了错误语义。在 fields.ts 中可以看到正确范例例如hideKnowledgeBaseTab的 hint 是 Revoke the Knowledge Base module. Members cannot open, search, or query any knowledge base.明确描述吊销的访问而非隐藏的界面。Step 2Schema、类型、默认值、解析器——验证派生而非手工复验写、读、容错三个 Schema、类型与默认值全部由collectFieldProperty从同一个注册表派生见 fields.ts因此不要手工复验它们。要验证的是没有任何东西绕过这条派生路径grep -rn configKey apps/sim --include*.ts --include*.tsx \ | grep -vE lib/permission-groups/(fields|resolve\.server|config-scope\.server)\.ts只有注册表和两个解析器被排除所以capabilities.ts会留在输出里——它的CAPABILITY_RULES条目和deniedBy才是本步骤要检查的权威读取点。每一个命中都应该是某条规则的deniedBy、一个强制点、一个 UI 绑定、或一个测试。一个重述 key 的路由、一个在客户端重新推导默认值的代码、或第二条强制转换路径coercion path都是泄漏leak。具体要警惕该 key 路径上任何z.array(...).catch(...)这是整体容错whole-value tolerant一个坏成员会丢弃所有好成员而放在 allowlist 上时null回退意味着不受限制即fail-open。对照 fields.ts 中的tolerantArray它用z.unknown().transform()逐元素safeParse保留幸存成员、失败时封闭fails closed。这种回归要与强制点缺陷同等排序。对 allowlist 使用?? []这把允许一切塌缩成什么都不允许。手写对照集成 allowlist 的比较allowedIntegrations组配置与部署的ALLOWED_INTEGRATIONS环境变量是独立书写的一边可能写slack另一边写slack_v2任何只折叠大小写的实现都会把两者相交为空集从而隐藏了双方都允许的集成。两侧在相交之前都必须经由 integration-allowlist.ts 规范化intersectAccessControlAllowlists/toAccessControlAllowlist/resolveAccessControlBlockType被检查的类型也要以同样方式解析。该模块通过Object.hasOwn读取block-successors.generated.ts——裸下标查找会用继承的函数回答管理员提供的constructor并在每个读取该组的强制路径上 500源码 integration-allowlist.ts 注释记录了这起事故check:block-successors负责在映射过期时让构建失败。任何不来自parsePermissionGroupConfig或resolvePermissionGroupConfig调用方的配置读取。两个结构性守卫必须仍然存在parsePermissionGroupConfig仍要测试Array.isArray(config)fields.tstypeof [] object列类型是jsonb一行数据确实可能存[]而z.object().parse([])会抛错——正是这个守卫让坏数据返回默认值而不是 500。tolerantArray是它的镜像。CAPABILITY_RULES仍使用satisfies而非类型注解注解会把StaticPermissionGroupCapability塌缩成never在运行时毫无征兆地静默关闭整个能力类型系统。AssertsStaticCapabilityResolves负责捕获capabilities.ts任何弱化都是最高优先级 finding。还要确认 fields.ts 底部的断言仍命名了该类型的字段AssertsAllowlistStaysPrecise、AssertsDenylistStaysPrecise、AssertsRestrictionStaysPrecise、AssertsAuthTypesStayPrecise、AssertsParserReturnsTheConfig——zod 泛型退化成unknown在运行时不可见却会悄悄丢失每个调用点的类型收窄。Step 3管理端 UIapps/sim/ee/access-control/components/group-detail.tsx布尔开关通过PLATFORM_FEATURES自动渲染见 features.ts从注册表派生保证一个布尔 key 不可能进入配置却进不了编辑器。确认其category在PLATFORM_CATEGORY_ORDER中features.ts未列出的分类会渲染在所有有序分区之后。嵌套 allowlist / denylist限定某个平台功能布尔的那一种除非它在featureExtras映射里——以父布尔的 feature id为键不是 config key——否则什么都不渲染。没有 picker 就意味着没有任何管理员能设置它上报即可。源码 group-detail.tsx 展示了正确示例allowedChatDeployAuthTypes挂在hide-deploy-chatbot下、allowedFileShareAuthTypes挂在disable-public-file-sharing下、allowedKnowledgeConnectors挂在hide-knowledge-base下。顶层列表——allowedIntegrations、allowedModelProviders、deniedModels、deniedTools——不在featureExtras中不应为它们上报缺失它们由专门的 Providers 与 Blocks 分区渲染要去那里检查。allowlist picker 两种行为都要查拒绝空选择源码中if (values.length 0) return见 group-detail.tsx空 allowlist 会静默阻断所有选项因此 UI 强制至少保留一项以及把全选塌缩回nullvalues.length ALL_KNOWLEDGE_CONNECTORS.length ? null : values否则 allowlist 会冻结在今天的成员上。denylist picker 两者都不做清空每一项正是管理员什么都不禁的方式全选则是全都禁的真实状态。检查父级是否正确allowedKnowledgeConnectors应挂在hide-knowledge-base下而非disable-knowledge-base-creation下——源码注释说明连接器附着在已存在的知识库上挂在创建下会让可同步但不可创建的组正好看到禁用的 pickergroup-detail.tsx。Step 4Capability 规则apps/sim/lib/permission-groups/capabilities.ts一个capability类型的 key 必须出现在某条规则的configKeys里——审计断言这一点断言 D及其反方向断言 E被规则读取的executor或ui-onlykey 会被标记因此 key 不可能在文档里写着更弱的语义时偷偷获得强制力。然后检查审计做不到的部分configKeys必须列出deniedBy读取的每一个 key。审计是文本解析从不读闭包一个被读取但未列出的 key 对 D 和 E 都不可见。kind是否正确。需要请求值的规则必须是parameterized——而一个命名在操作上的 parameterized 规则不可能在生产运行过defineWorkspaceOperation在定义时就抛错所以如果看到了说明有别的地方坏了。更窄的 capability 必须包含subsume它替换掉的更宽的 capability。一个操作恰好携带一个 capability。先例knowledge.create/knowledge.upload都读hideKnowledgeBaseTabcapabilities.ts否则一个吊销了整个模块的组还能通过 API 创建知识库。用git log检查被重新指向的capability:并确认更窄规则在同一提交里长出了更宽的 key。detailCode要与 remedy 匹配FORBIDDEN_DETAIL_CODES是对 remedy 封闭而非对原因封闭否则会变成PERMISSION_GROUP_CAPABILITY_BLOCKED。任何在用的 code 都需要FORBIDDEN_DETAIL_CODE_DESCRIPTIONS里有条目——这是个编译期门同时发布 OpenAPI 的 403 文案。describe要能作为句子的主语读通describe is not available under your organizations permission group——单数名词或动名词与 is 一致。构建这句话的函数恰好两个都在capabilities.tsrefuseCapability抛出它、capabilityRefusal返回它见 capabilities.ts任何手写这句话的调用点都是 drift finding。Step 5证明强制执行——不要假设它存在这是本技能存在的理由。找到真正的拒绝点报出文件与行号说清调用方会看到什么grep -rn capability-id apps/sim --include*.ts --include*.tsx grep -rn permission-group-enforced: capability-id apps/sim第二条 grep 会漏掉注解位于外层语句上方 TSDoc 块中的门——记得读周围函数。把结果归入且只归入以下五类之一1. 声明在操作上Declared on operations。漏斗在requireCurrentHumanAccess→requireCapability中执行workspace-authorization.ts。验证集合完整枚举每一条到达同一行为的路由和工具其中一个声明capability: none的就是漏洞。2. 在调用点断言Asserted at a call site带// permission-group-enforced: id — reason注解。验证它走capability-assertions.tsassertWorkspaceCapability、isWorkspaceCapabilityWithheld、isOrganizationCapabilityWithheld、capabilityDeniedBy见 capability-assertions.ts、或isCapabilityWithheldForUseruser-scope.server.ts先查工作区组、否则查组织默认组用于可能命名也可能不命名工作区的用户级行为它有意识地放在capability-assertions.ts之外因为它通过计费图读取组织成员关系而那是check:application-graph的受保护根app/api/cli/auth/approve/route.ts是其形态、或直接CAPABILITY_RULES[id].deniedBy(...)而不是内联读config.disableX。并且它必须通过refuseCapability抛出 / 渲染capabilityRefusal(cap)而不是用一句手写消息构建自己的ForbiddenOperationError——这是最容易漏掉的一半因为决策看起来是对的。用例形态validatePublicFileSharing、validateChatDeployAuthee/access-control/utils/permission-check.ts、assertConnectorTypeAllowedlib/knowledge/application/connectors.ts。裸路由形态app/api/logs/stats/route.ts、app/api/table/[tableId]/export/route.ts。裸路由应通过capabilityRefusalResponse渲染capability-response.ts它从规则上读details.code——手写NextResponse.json({ error: capabilityRefusal(cap) }, { status: 403 })会丢掉它把四个带专门 code 的 capabilitydeploy.chat.auth_mode、file_share.publish、file_share.auth_mode、personal_api_key.use报成通用阻断。收敛是部分的grep -rln capabilityRefusal( apps/sim/app --includeroute.ts能列出仍在手写它的裸路由忽略*.test.ts、app/api/v1/middleware.ts、app/api/table/utils.ts与 v2 信封它们不是裸路由响应。你新增或触碰的裸路由要渲染走capabilityRefusalResponse对未触碰的手写路由当它的 capability 带专门 code 时作为 finding 上报否则作为 note。v1 刻意不收敛见app/api/v1/middleware.ts的resolveCapabilityRefusal。3. Executor 门控由assertPermissionsAllowed按 block / tool / model 执行匹配走 lib/permission-groups 下的共享原语——block-access.ts、operation-access.ts、model-access.ts、integration-allowlist.ts——编辑器和 Copilot 投影也读这些原语所以第二份匹配规则副本就是 finding。验证该分支抛出真实错误且它比较的 id 是管理端 UI 写入的词汇——deniedTools原样持有 blocktools.accessid含版本后缀。allowedIntegrations在运行之外也由assertSelectorIntegrationAllowedlib/selectors/server/integration-access.ts执行所以 executor key 的覆盖要一直检查到每个非运行路径中触达第三方的环节。4. 字段投影不是门A field projection, not a gate。logs.trace_spans和logs.cost是扣减字段所以日志路由正确地声明capability: none。唯一所有者lib/logs/log-projection.tsresolveLogFieldProjection、projectExecutionData、projectCostTotal它带着两条注解。第二份同样的脱敏实现就是 finding——让调用方在扣减字段上过滤或排序的查询也是 finding它把投影变成了预言机oracle。5. 什么都没有Nothing。作为缺陷上报设置了这个的组织认为它施加了一项并不存在的限制。在这五类之前还有一个特例personal_api_key.use哪一类都不属于。它扣减的是跨所有操作的主体种类principal kind——漏斗的personal_api_key分支workspace-authorization.ts和app/api/v1/middleware.ts——所以没有操作声明它disablePersonalApiKeys不出现在任何capability:字段里是正确的不是漏洞。然后让拒绝真正发生写一个会失败的用例或者移除门capability:字段、deniedBy函数体、断言调用并确认某个既有测试变红。门移除后依然通过的测试什么也证明不了。完成后恢复。先检查 fixture 的workspaceOrganizationIdrequireCapability在它为null时短路workspace-authorization.ts所以一个没设它的上下文怎么都通过既有测试在你动手之前就已证明不了任何事。对 allowlist三个状态必须分开测——null允许每个成员、填充列表只允许名单内的、[]一个都不允许。capabilities.test.ts为knowledge.connectors钉住了全部三态capabilities.test.ts别处比这少就是缺口。门对谁运行读主体别读最近的 user id每个 capability 汇点sink都必须从该表面持有的身份对应的capabilityGoverned*辅助函数取主体——Principal用capabilityGovernedPrincipalUserIdlib/core/application、v1RateLimitResult或TableAccessPrincipal用capabilityGovernedUserId、checkSessionOrInternalAuth结果用capabilityGovernedAuthUserId。每个都返回null无组治理时而null是放行。把rateLimit.userId、auth.userId、subjectUserId或triggeredByUserId直接读进汇点是 finding对工作区 key前者是 key 的创建者对内部 JWT第二个是运行的执行者最后一个是计费的归因。check-capability-subject.ts只审计 v1所以其余每个表面都要自己查。当主体被持久化并在之后读回时table_run_dispatches/table_row_executions上的capabilityGovernedUserId它必须声明为必填的string | null——带回退的可选字段正是生产者重新继承triggeredByUserId的方式所以把它改成可选的提议就是 finding。/api/v1在app/api/v1/middleware.ts授权不走authorizeWorkspaceOperationcapabilityGovernedUserId(rateLimit)按keyType分支绝不在是否存在 user id上分支。每条路由还穿带一个必填、拼写完整的V1RouteCapability。裸内部表路由在checkAccessapp/api/table/utils.ts里经TableAccessPrincipal联合类型门控tables.use——{ kind: user; userId }或{ kind: workspace_api_key; keyCreatorUserId }——所以裸 id 无法通过类型检查。tableAccessPrincipal(rateLimit)是 v1 构建它的唯一位置。defineWorkspaceOperation上的定义期undefined守卫并非冗余即使capability在ApplicationOperation基础类型上是必填lib/core/application/operation.ts的capability字段而不只是 builder 上的——正是它挡住了裸字面量工厂的编译apps/sim/tsconfig.json排除了*.test.ts/*.test.tsx且强制审计会走过测试文件所以 fixture 是唯一没有任何静态检查读取的构造点。没有它无 capability 的操作能干净地定义然后在capabilityDeniedBy内部抛Cannot read properties of undefined——只对真正有权限组的租户触发CI 与所有个人工作区照常通过。提议删除它是 finding。Step 6测试fields.test.ts——key 必须同时出现在a fully populated configfixture 的input和expected两半里这个语料是被钉死的所以某行被改动会触发防御而不是悄悄溜过。文件其余部分从DEFAULT_PERMISSION_GROUP_CONFIG派生无需按 key 编辑。capabilities.test.ts——任何逻辑不止读一个 key 的规则都要有用例包含关系subsumption、allowlist 三态、auth-mode 成员关系。features.test.ts——布尔无需编辑非布尔 key 的limited/empty文案应在此钉住。config-scope.server.test.ts——每请求备忘录。在resolvePermissionGroupConfig之外解析配置的门是 Step 2 的 finding不是这里的。Step 7运行检查bun run check:permission-group-enforcement bun run check:application-graph bun run check:capability-subject cd apps/sim bun run type-check bunx vitest run lib/permission-groups三条审计都在check:audits内package.json它从package.json的check:*脚本列表派生——新审计是刻意 opt-out 的。读输出别只读退出码。成功行形态如下计数必须包含被审计的条目✓ permission-group enforcement: N operations declare a capability, M capabilities all enforced ✅ Application graph clean: N roots reach none of M forbidden module trees check:capability-subject — N v1 files, M capability subjects resolved through capabilityGovernedUserId.审计它能抓住什么check:permission-group-enforcement每个操作声明一个 capability且每个 capability 都被强制执行。全有或全无——没有待办 enforcement 列表的迁移模式能带着未完成工作以 0 退出所以不要去找什么pending enforcement:列表check:application-graph漏斗根lib/core/application/index.ts、capabilities.ts、capability-assertions.ts、config-scope.server.ts与with-route-handler.ts在运行时不触达任何重型模块树import type会被擦除允许。把 resolver 导入受保护根的门即使本身正确也是 finding过去的回归只表现为不相关测试在部分 mock 上失败check:capability-subject每个 v1 capability 汇点从capabilityGovernedUserId取主体、没有 v1 文件在 middleware 之外导入 permission-group 模块、且至少找到了一个受治理的汇点有两种方式让强制审计通过却不证明你想要的东西空转解析vacuous parse。它用正则读源码文本所以当三个注册表解析为空时会拒绝成功、把规则数与 capability 数交叉核对、对每个不可读的id按调用上报、让铸出一个操作却解析出零个声明的文件失败、并标记任何它没读到操作来源的导出*Operations注册表成员check-permission-group-enforcement.ts 中的断言 F——它抓住的是由从不调用 builder 的工厂铸出的操作这种操作同时绕过了必填类型和审计。如果其中一条触发是审计坏了不是代码坏了——修解析器别让它保持绿色。声明在没有任何路由到达的操作上的 capability。断言 C 仅凭声明本身就满足了。这些审计证明的是可达性reachability永远不是正确性——某个 capability 被命名、某个 key 被某条规则读取、某个主体来自正确的辅助函数。Step 5 覆盖其余的。已知缺口识别它们不要重复上报每一条都是刻意设计并记录在代码中的完整理由见add-permission-group-item工作区 API key 不解析任何权限组——它以工作区身份授权没有用户operation.capability不适用同样的推理塑造了TableAccessPrincipal、capabilityGovernedUserId与日志投影。用 key 的创建者来替代会把旁观者的组套到共享 key 的每个调用者头上并在创建者离开时弄坏 key。铸造工作区 key 本身是受 capability 门控的。executor 委托携带角色但不携带 capability——带sim_user主体的委托executor主体只走requireCurrentHumanRole。capability 命名的是一个人能触达什么把它套到运行上会把隐藏 Tables变成每个带 Table block 的工作流的杀毒开关。无 actor 的部署运行直接通过——mode: deployment中无可解析主体的委托 executor 主体以工作区权限行动拒绝会 403 掉每个定时运行、webhook 和公开 API 调用。这样的运行做什么仍受assertPermissionsAllowed治理这正是四个 run 级 key 携带enforcement: executor的原因。Copilot 并不豁免——serviceId不是executor的带sim_user主体的委托主体走完整的requireCurrentHumanAccess。Copilot 以人的身份行动。提议豁免它是 finding。capability 在角色检查之后——v2 表面把NoWorkspaceAccessError伪装成 404所以先拒绝 capability 会给非成员一个组织扣减了什么的预言机。v1 middleware 在其 TSDoc 里声明了同样的顺序。不是 bug。allowedEgressHosts不存在——没有网络出口 allowlist。索要它是功能请求不是缺失的接线。当前没有任何东西以ui-only形态交付——这个联合成员没有用户缺失ui-onlykey 不是缺口。报告格式种类与强制方式Kind and enforcement——按声明以及声明是否为真。拒绝点The refusal——文件、行、抛出的错误、调用方看到什么状态码、detailCode、消息。或者它是投影这里是它的唯一所有者。或者没有任何东西拒绝。主体The subject——门读取的是谁的 user id以及工作区 key 以自身而非其创建者身份无门控到达。证明Proof——移除门时会失败的测试或证实不存在这样的测试。覆盖缺口Coverage gaps——到达同一行为却没有门的路由、工具、表面。发现Findings按此顺序未强制的 key 用 key 创建者替代行动主体 fail-open 强制转换数组上的.catch()、丢失的Array.isArray守卫、被注解而非satisfies的CAPABILITY_RULES 操作覆盖不完整 allowlist 三态混淆 把强制方式写错的 admin 文案 重复的投影逻辑 缺失管理端 UI 缺失测试 外观问题。一个对会 403 的 key 说 hide 的 hint 不是外观问题——它是管理员会直接据以行动的那一个缺陷他们勾选它以为藏起了链接而成员失去了整个模块。要把它与强制点发现排在一起。小结这套七步流程的价值在于把看起来正确与确实拒绝分开注册表与satisfies、类型断言解决了结构性存在Step 5 的移除门 → 测试变红是行为性证明三条check:*审计兜底可达性与主体来源已知缺口清单避免重复上报。对 Sim 中任何PERMISSION_GROUP_FIELDS键或CAPABILITY_RULES能力的评审、扩展与回归防护都可以直接套用本清单与报告模板。【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000 builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表