关于 acl-security
面向 ServiceNow 开发与管理人员的 ACL 安全模式指南,说明访问控制规则的匹配方式、角色检查和常见配置。重点是汇总继承表与通配名称,并指出诊断工具的边界及创建 ACL 时的安全步骤。
主要功能
- 01梳理记录级与字段级 ACL
- 02比较继承表和通配规则
- 03解释规则角色差异
- 04介绍 ACL 创建与检查步骤
- 05提供常见访问控制模式
适用场景
- 01排查用户无法访问记录或字段
- 02规划基于角色或所有权的访问
- 03创建 ACL 并核对角色配置
MCP 相关
文档介绍 ServiceNow MCP 工具,包括汇总适用 ACL 的 snow_acl_explain、创建 ACL 的 snow_create_acl,以及用于审计模拟用户的 snow_impersonate_user。它提醒不要依赖会误报决策的 snow_test_acl。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ serac-labs/ serac --skill acl-security安装后在 Claude Code 中输入/acl-security确认已加载。
详细介绍
ServiceNow 的 ACL 安全模式
访问控制列表(ACL)是 ServiceNow 安全性的基础。它们控制谁可以读取、写入、创建和删除记录。
可能适用的 ACL
ACL 按名称应用,名称是字面值——sys_security_acl.name 存储的是 incident.priority 或 *,绝不是 glob 模式。对于继承自其他表的表上的某个字段,以下六种名称都可以各自携带一条规则:
| 名称 | 承载的规则 |
|---|---|
incident.priority | 此表上的此字段 |
task.priority | 父表上的此字段 |
*.priority | 任意表上名称为此字段的字段 |
incident.* | 此表上的任意字段 |
task.* | 父表上的任意字段 |
*.* | 任意表上的任意字段 |
此外还有记录级名称:incident,然后是 sys_db_object.super_class 链中的每个父表,最后是 *。
其中两种形式已通过实际运行的实例得到确认:记录级的 *(sn-roles.manifest.json 背后的 ACL 探测通过它解析了 189 个工具原语)以及 table.field(snow_analyze_form 使用 nameSTARTSWITH<table>. 读取字段 ACL)。上文关于 *.priority、incident.* 和 *.* 的说明遵循相同的命名规则,但并非来自 ServiceNow 官方文档——请将它们视为值得检查的名称,而不是有文档依据的行为。无论如何,snow_acl_explain 都会检查所有这些名称;无人使用的名称只会返回空结果。
请全部检查。只检查 incident.priority,就会在你正在排查的特定情况下指出错误的阻碍项——也就是承载角色要求的规则位于 task 或通配符上时。
snow_acl_explain 会在一次调用中收集完整集合:它会遍历 sys_db_object.super_class 获取父表名称,读取每条匹配规则在 sys_security_acl_role 多对多关系中的必需角色(sys_security_acl 上没有 roles 列,因此读取该列的工具会报告完全没有角色),并将这些角色与用户在 sys_user_has_role 中持有的角色进行比较——其中已包含继承的角色,因此无需自行展开 sys_user_role_contains。
请正确解读这个差异。单条规则中的角色是可选关系,因此规则的 roles_not_held 是 requires_roles 中剩余的部分,绝不是要授予的角色列表:如果该规则的 user_holds 非空,说明其角色检查已通过,阻碍项在其他地方——可能是另一层规则,也可能是工具未评估的条件或脚本。
它不会替你做出判断。条件和高级脚本会在平台内部针对某条特定记录运行,而 REST Table API 中没有任何接口会评估它们。因此,应将输出理解为“以下是适用的规则以及用户持有的角色”,然后通过模拟用户身份进行确认。snow_test_acl 声称会返回判断结果:它会向 sys_script_execution POST 一条记录,并将 Table API 的回显报告为脚本输出,同时忽略 operation 和 user 参数。请勿使用它。
ACL 类型
| 类型 | 控制范围 | 示例 |
|---|---|---|
| record | 行级访问 | 用户能查看此事件吗? |
| field | 字段级访问 | 用户能查看 assignment_group 吗? |
| client_callable_script_include | Script Include 访问 | 用户能调用此 API 吗? |
| ui_page | UI Page 访问 | 用户能查看此页面吗? |
| rest_endpoint | REST API 访问 | 用户能调用此端点吗? |
通过 MCP 创建 ACL
snow_create_acl 只会创建 ACL 框架,不会执行其他操作。它接受 name、operation、type、admin_overrides 和 active;没有 roles、condition 或 script 参数,即使传入这些参数也会被静默忽略——它们不会传到 sys_security_acl。处于此阶段的 ACL 没有指定任何角色,也没有条件,这是你能编写的权限最宽松的规则。在完成另外两个步骤之前,请将 active: false。
// 1. The shell — a record-level WRITE ACL on incident, created inactive.
snow_create_acl({
name: "incident",
operation: "write",
type: "record",
admin_overrides: true,
active: false,
})
// 2. The roles. They live in the sys_security_acl_role m2m, one row per role, so
// this is one call per role and each one needs that role's sys_id.
snow_query_table({ table: "sys_user_role", query: "nameINitil,incident_manager", fields: ["sys_id", "name"] })
snow_create_acl_role({ acl: "<acl sys_id>", role: "<itil sys_id>" })
snow_create_acl_role({ acl: "<acl sys_id>", role: "<incident_manager sys_id>" })
// 3. Condition and script are columns on the ACL record itself, with no argument
// on snow_create_acl. Set them by updating the record, then activate it.
snow_record_manage({
action: "update",
table: "sys_security_acl",
sys_id: "<acl sys_id>",
data: { script: "answer = current.state < 6;", active: true },
})
同一个 ACL 上的两个角色是可选关系——任一角色都能根据该规则授予访问权限——因此,步骤 2 会通过每一行扩大规则范围,而不是缩小范围。
condition 和 script 都是 sys_security_acl 上的真实列(snow_analyze_form 会读取它们)。请按照 ACL 表单中 Condition 和 Script 字段所需的格式分别填写。
字段级规则的三个步骤相同,只需将 name 设为 "incident.priority"。
然后使用 snow_acl_explain({ table: "incident", operation: "write", user: "<someone>" }) 检查配置——它会从 m2m 中读取角色,而这一步最容易被遗漏。
常见 ACL 模式
模式 1:基于角色的访问
// Condition: (empty - role check only)
// Roles: itil, incident_manager
// Script: (empty)
// Users with itil OR incident_manager role can access
模式 2:基于所有权的访问
// Condition:
current.caller_id == gs.getUserID() || current.assigned_to == gs.getUserID() || current.opened_by == gs.getUserID()
// User can access their own records
模式 3:基于组的访问
// Script:
;(function () {
var userGroups = gs.getUser().getMyGroups()
answer = userGroups.indexOf(current.assignment_group.toString()) >= 0
})()
// User can access records assigned to their groups
模式 4:基于经理关系链的访问
// Script:
;(function () {
var callerManager = current.caller_id.manager
var currentUser = gs.getUserID()
// Check if current user is in caller's management chain
while (callerManager && !callerManager.nil()) {
if (callerManager.toString() == currentUser) {
answer = true
return
}
callerManager = callerManager.manager
}
answer = false
})()
模式 5:基于时间的访问
// Script:
;(function () {
var now = new GlideDateTime()
var hour = parseInt(now.getLocalTime().getHourOfDayLocalTime())
// Only allow access during business hours (8 AM - 6 PM)
answer = hour >= 8 && hour < 18
})()
模式 6:数据分类
// Script:
;(function () {
var classification = current.u_data_classification.toString()
var userClearance = gs.getUser().getRecord().getValue("u_security_clearance")
var levels = { public: 0, internal: 1, confidential: 2, secret: 3 }
answer = levels[userClearance] >= levels[classification]
})()
字段级安全模式
隐藏敏感字段
// ACL: incident.u_ssn (Social Security Number)
// Operation: read
// Script:
answer = gs.hasRole("hr_admin")
// Only HR admins can see SSN field
状态更改后设为只读
// ACL: incident.short_description
// Operation: write
// Script:
answer = current.state < 6 // Can't edit after Resolved
// Prevent editing after resolution
条件式字段可见性
// ACL: incident.u_internal_notes
// Operation: read
// Condition:
gs.hasRole("itil") || current.caller_id == gs.getUserID()
// ITIL users see all, callers see their own
安全最佳实践
1. 最小权限原则
// ❌ BAD - Too permissive
// Roles: (empty) - allows everyone
// ✅ GOOD - Explicit roles
// Roles: itil, incident_manager
2. 默认拒绝
// Create a catch-all deny ACL at lowest priority
// Name: *
// Operation: read
// Condition: false
// This ensures anything not explicitly allowed is denied
3. 避免复杂脚本
// ❌ BAD - Complex script ACL (slow)
;(function () {
var gr = new GlideRecord("sys_user_grmember")
gr.addQuery("user", gs.getUserID())
gr.query()
while (gr.next()) {
// Complex logic...
}
})()
// ✅ GOOD - Use conditions when possible
// Condition: gs.getUser().isMemberOf(current.assignment_group)
4. 彻底测试 ACL
// Use "Impersonate User" to test ACLs as different users
// Check: Navigation, List views, Forms, Related lists
// Verify: Fields hidden, buttons disabled, records filtered
使用 snow_impersonate_user 生成经过审计的模拟用户深层链接(仅限管理员;会写入 ~/.serac/audit/impersonations.jsonl)。在诊断 ACL 故障前,使用 snow_session_context 确认调用者当前的角色和更新集。
调试 ACL
启用 ACL 调试
// In a background script or temporarily in your code:
gs.setProperty("glide.security.debug", "true")
gs.log("ACL Debug enabled")
// Check System Logs for ACL evaluation details
检查用户权限
// Check if current user can read a record
var gr = new GlideRecord("incident")
gr.get("sys_id_here")
gs.info("Can Read: " + gr.canRead())
gs.info("Can Write: " + gr.canWrite())
gs.info("Can Delete: " + gr.canDelete())
// Check field-level
gs.info("Can read assignment_group: " + gr.assignment_group.canRead())
gs.info("Can write assignment_group: " + gr.assignment_group.canWrite())
常见错误
| 错误 | 问题 | 解决方案 |
|---|---|---|
| 自定义表上没有 ACL | 任何人都能访问 | 立即创建 ACL |
| 只有基于角色的 ACL | 没有行级安全 | 添加条件以隔离数据 |
| 查询数据库的脚本 | 性能问题 | 使用条件或缓存结果 |
| 只以管理员身份测试 | 管理员会绕过 ACL | 以实际终端用户身份测试 |
| 忘记 REST API | API 会绕过 UI ACL | 创建专用的 REST ACL |