关于 descope-fga-schema
用于设计和应用 Descope FGA 模式的技能,按起草、试运行、审阅和确认的流程操作,并强调安全编辑与验证。
主要功能
- 01起草 FGA DSL 模式
- 02通过试运行验证
- 03应用前审查删除风险
- 04遵循语法和命名规则
- 05优先使用权限与内置约束
适用场景
- 01创建新的授权模式
- 02安全扩展现有模式
- 03检查模式变更的数据丢失风险
MCP 相关
需要连接 Descope Management MCP,并使用 GetFGASchema、DryRunSchema 和 CreateFGASchema 操作读取、验证和应用模式。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ descope/ skills --skill descope-fga-schema安装后在 Claude Code 中输入/descope-fga-schema确认已加载。
详细介绍
FGA DSL 编写
帮助用户设计并应用 Descope FGA 架构。工作流程:了解需求 → 起草 DSL → 通过试运行验证 → 向用户展示并说明任何数据丢失警告 → 获取确认 → 应用。
MCP 设置 — 首先检查,缺失时停止
在执行任何其他操作之前,检查 Descope Management MCP 是否已连接:查找名称中包含 FGASchema 或 DryRunSchema 的工具(例如 mcp__descope__DryRunSchema)。确切前缀取决于用户安装 MCP 的方式,但操作 ID 为 DryRunSchema、CreateFGASchema 和 GetFGASchema。
**如果未找到这些工具:**只输出以下消息,然后结束当前轮次。不要生成架构,不要说“连接后我会应用以下内容”,不要进行任何设计工作,也不要继续:
The Descope Management MCP is required. If not yet installed, install and authorize it, then restart your AI agent and re-run
/descope-fga-schema. See https://docs.descope.com/mcp/mcp-server#connecting for setup instructions. If already installed, it may need authorization. Authorize the Descope MCP, then restart your AI agent and re-run/descope-fga-schema.
**如果找到了这些工具:**立即调用 GetFGASchema,在进行任何其他工作之前先进行连接探测。如果此调用返回授权错误,只输出以下消息,然后结束当前轮次:
The Descope MCP is installed but not authorized. Authorize it, restart Claude Code, and re-run
/descope-fga-schema.
所有 FGA 操作都必须通过 MCP 工具调用完成,绝不能自行发送原始 HTTP 请求。
连接后,使用 GetFGASchema 工具读取当前架构;当用户要求修改现有架构时,务必执行此操作。
语法
每个架构必须以以下内容开头,且必须完全一致:
model AuthZ 1.0
API 不接受任何其他名称或版本。
完整结构:
model AuthZ 1.0
[constraint <Name>[:<Kind>][(args...)]]*
[condition <Name>(<param type, ...>) { <CEL bool expr> }]*
type <TypeName>
[relation <name>: <TypeRef> [| <TypeRef>]* [with <condExpr>]]*
[permission <name>: <expr> [with <condExpr>]]*
关键字:model type relation permission condition constraint with
运算符:
- 权限表达式:
|表示并集,&表示交集,-表示差集。运算符可以与括号混用:a | (b - c) - 集合箭头:
relation.permission— 遍历已存储的关系,以访问主体自身的权限(例如parent.can_view) - 目标集合:
Type#relation— 请参阅下方的专门章节 with子句(适用于关系和权限):&表示 AND,|表示 OR,!表示 NOT,括号:with A & (B | !C)。条件在检查时求值——with用于控制求值时关系或权限是否计入结果。每个关系或权限定义只能有一个with子句;多个条件应在其中使用&/|/!组合。
不允许注释——DSL 解析器不支持注释标记。
命名规则:类型、条件和约束使用 PascalCase。关系和权限使用 snake_case。
目标集合模式(Type#relation)
当某个关系应由某个组的成员持有时(例如“此 Team 的任何成员”),请直接在关系定义中使用 Type#relation。这样存储的是各个成员主体——这是进行权限检查时合适的粒度。
间接方式——存储组本身,再通过权限推导成员关系——也能正确展开关系,但会引入一个自身没有语义意义的 contributor_team 关系。唯一有意义的实体是单个成员。目标集合语法更简洁,也能直接表达意图。
避免(增加了没有语义价值的关系):
type Repository
relation contributor_team: Team
permission contributor: contributor_team.member
推荐(简洁、直接):
type Repository
relation contributor: Team#member
可以混用直接主体和目标集合主体:relation editor: User | Team#member
应避免的 ABAC 反模式
切勿使用「blocked」关系加减法来表达条件
with 条件在检查时求值——即根据请求中传入的上下文进行权限检查时。关系始终会无条件存储;条件只会影响权限评估时该关系是否生效。
「blocked」关系加减法模式是错误的,因为它要求为每个被排除的用户,在数据库中手动维护单独的「blocked」边。这不是正确的工具:应在授予访问权限的关系上使用 with !Condition——它会在检查时自动求值,无需额外存储关系。
// NEVER do this — requires maintaining a separate "blocked" edge per user in the DB
relation creator: User
relation blocked: User with NorthKorea
permission can_delete: creator - blocked
// Right — condition evaluated automatically at check time; no extra edges
relation creator: User with !NorthKorea
permission can_delete: creator
内置约束可以满足需求时,不要编写自定义 CEL
用来检查数值范围的自定义 condition,不过是在重新实现 NumRange(或 NumAtLeast/NumAtMost)。内置约束更简洁、不易出错,还能形成一套通用词汇,让人和 Agent 都更容易阅读和理解 schema。请使用内置约束。
错误:
condition DuringBusinessHours(seconds_since_midnight int) { seconds_since_midnight >= 32400 && seconds_since_midnight < 61200 }
正确:
constraint BusinessHours:NumRange(32400, 61200)
(如果想为约束起一个描述性名称,请使用命名别名。)
关系与权限
关系会在纯关系图中添加一条边。权限是复用现有关系的派生规则——它只会在 ReBAC 图中添加边,而不会引入新的纯图边。纯图边越少,检查时需要遍历的边就越少,并且 schema 中所有检查命中缓存的可能性也越高,因此权限写法更简洁;保持纯图精简也有助于随着系统扩展提升整体检查性能。只要可行,就优先通过权限满足需求。只有确实需要直接存储链接时,才引入新关系。
当某个权限严格包含另一个权限时,应通过引用范围更窄的权限来表达,而不是重复展开其内容。这样可以让 schema 更简洁,并使访问层级自我说明——读者能立即看出 can_admin 蕴含 can_write,而 can_write 又蕴含 can_read。
避免(在权限间重复关系):
permission can_admin: owner
permission can_write: owner | editor
permission can_read: owner | editor | viewer
建议(每个权限都基于前一个权限构建):
permission can_admin: owner
permission can_write: can_admin | editor
permission can_read: can_write | viewer
内置约束
优先使用内置约束,再考虑自定义 CEL。
| 约束 | 运行时参数(零参数形式) | 硬编码形式 |
|---|---|---|
IpRange | ip ipaddress, ip_range string | IpRange("10.0.0.0/8") |
IpList | ip ipaddress, allowed_ips list | IpList("1.2.3.4","5.6.7.8") |
DateExpiryEpochSeconds | now_epoch_seconds int, expiry_epoch_seconds int | DateExpiryEpochSeconds(1735689600) |
StringMatchRegex | str string | StringMatchRegex("^admin_.*")(必须使用正则表达式) |
NumAtLeast | num double, min int | NumAtLeast(18) |
NumAtMost | num double, max int | NumAtMost(100) |
NumRange | num double, min int, max int | NumRange(0,100)(min ≤ max) |
BoolCheck | bool bool, expected bool | BoolCheck(true) |
GeoCountry | country_code string, allowed_countries list | GeoCountry("US","GB")(ISO 3166-1 alpha-2) |
IntList | int int, allowed_ints list | IntList(1,2,3) |
LabelList | label string, allowed_labels list | LabelList("foo","bar") |
多个相同类型的约束: 不使用命名别名时,不能多次声明同一种约束——必须使用别名来区分。命名别名与原始约束类型共享相同的运行时参数名称(别名只会更改约束的标识符,不会更改其参数)。当两个约束作用于同一个参数时,这样做没有问题。如果需要两个约束分别作用于不同的参数,请改用带有唯一参数名的自定义 CEL 条件:
// Two GeoCountry constraints sharing the same country_code param — alias required, shared param is intentional
constraint FiveEyes:GeoCountry("US","GB","CA","AU","NZ")
constraint Sanction:GeoCountry("KP","IR","SY","RU")
// Need a second IP check with a different param name? Use a custom condition
condition OfficeNetwork(office_ip ipaddress, office_range string) { office_ip.in_cidr(office_range) }
自定义 CEL——仅在没有内置约束能覆盖所需逻辑,或基于别名的参数分离方式不够用时使用:
condition InNetwork(user_ip ipaddress, allowed_range string) { user_ip.in_cidr(allowed_range) }
CEL 参数类型:int、string、bool、double、list、ipaddress。主体必须返回 bool。避免嵌套使用 exists——求值器会强制执行成本限制。
编辑安全协议
编辑现有架构时,首先使用 GetFGASchema 读取当前架构,以获取实际状态。
- 如果用户要求添加的内容已经存在,请准确告知现有内容并停止——不要悄悄覆盖。
- 从架构中移除整个类型或关系定义,会导致数据库中该类型或关系的所有关系元组被永久删除。编辑关系定义的目标类型,相当于删除并重新创建该关系定义——同样存在数据丢失风险。务必先向用户确认,并确保他们了解影响,然后再继续。
- 例外:仅编辑关系的
with条件不会删除元组。 关系会无条件存储;检查时才会评估条件。在其他内容不变的情况下,将with CondA改为with CondB会保留所有现有元组——只不过之后会根据新条件进行评估。这比完整编辑关系更安全,但仍需谨慎:依赖旧条件行为的调用方,在变更后会得到不同的访问结果。 - 移除或编辑权限不会删除关系元组,但任何依赖该权限的下游权限或检查都会悄然失效。请先向用户确认。
- 添加新类型、关系或权限通常是安全的。
验证与应用工作流
每次生成或编辑 DSL 时,都按以下顺序操作:
第 1 步——试运行
使用 DryRunSchema MCP 工具运行提议的 DSL。此操作会验证架构,并报告应用后会删除哪些数据。
- 出错时:架构无效。阅读错误消息,修复 DSL,然后重试。最多重试 5 次——如果仍然失败,请停止并向用户展示最后一条错误。
- 成功时:继续执行第 2 步。
响应包含:
{
"deletesPreview": {
"hasDeletes": true,
"relations": ["folder#viewer", "doc#editor"],
"types": ["LegacyRole"]
}
}
步骤 2 — 展示给用户
展示:
- 完整的 DSL(使用代码块格式)
- 如果
hasDeletes为 true——明确警告列出将从数据库中永久删除的每种关系类型和命名空间类型
警告示例:
警告:应用此架构将永久删除以下类型的所有已存储关系:
folder#viewerdoc#editor此操作无法撤销。请确认是否继续。
如果 hasDeletes 为 false,只需展示架构并请求确认。
步骤 3 — 获取确认
在步骤 2 后结束本轮。不要在调用 DryRunSchema 的同一轮中调用 CreateFGASchema——用户必须先看到架构和任何删除警告,然后才能继续。等待用户明确批准(例如“是”“应用”“继续”等)。
步骤 4 — 应用
调用 CreateFGASchema 前,确认以下三项均成立:
- 你已在之前的某一轮(而非本轮)用代码块展示了完整 DSL
- 你已说明 dry-run 响应中的所有删除警告(或已确认
hasDeletes为 false) - 用户最近一条消息是对你确认提示的明确批准
如果有任何一项不成立,不要调用 CreateFGASchema。请返回步骤 2。
满足以上三项后,使用 dry-run 中的同一 DSL 调用 CreateFGASchema。向用户确认成功。
设置此门槛的原因:CreateFGASchema 不可逆。架构变更删除的关系元组无法恢复。即使变更看起来很小,也绝不能跳过确认。
示例
基本 ReBAC 与层级结构
model AuthZ 1.0
type User
type Folder
type Doc
relation owner: User
relation parent: Folder
permission can_view: owner | parent.owner
permission can_edit: owner
通过目标集实现群组成员关系
model AuthZ 1.0
type User
type Team
relation member: User
type Repository
relation owner: User
relation contributor: User | Team#member
permission can_push: owner | contributor
permission can_read: can_push
ABAC:基于时间的访问控制
model AuthZ 1.0
constraint ShiftHours:NumRange
type User
type PatientRecord
relation viewer: User with ShiftHours
relation owner: User
permission can_view: viewer | owner
重用带别名的约束类型——以及在权限上使用 with
model AuthZ 1.0
constraint FiveEyes:GeoCountry("US","GB","CA","AU","NZ")
constraint Sanction:GeoCountry("KP","IR","SY","RU")
constraint OfficeOnly:IpRange("10.0.0.0/8")
type User
type Resource
relation allowed: User with FiveEyes & !Sanction
relation owner: User
permission can_access: allowed
permission can_delete: owner with OfficeOnly
allowed 在关系上应用地理位置限制——所有使用 allowed 的权限都会受此限制。can_delete 在权限本身使用 with,因此 IP 限制仅作用于删除操作,而不作用于访问。
使用 with 的嵌套权限——条件会累积
model AuthZ 1.0
constraint BusinessHours:NumRange(32400, 61200)
constraint OfficeNetwork:IpRange("10.0.0.0/8")
type User
type Document
relation reader: User
permission can_read: reader with BusinessHours
permission can_edit: can_read with OfficeNetwork
can_edit 同时要求 BusinessHours(来自 can_read)和 OfficeNetwork(来自 can_edit 自身的 with)。检查时必须同时满足两个条件——嵌套权限上的 with 子句会累积。