mcpField
A
acl-security

acl-security

github.com/serac-labs安全7761 次安装9 周前更新
代码仓库

关于 acl-security

面向 ServiceNow 开发与管理人员的 ACL 安全模式指南,说明访问控制规则的匹配方式、角色检查和常见配置。重点是汇总继承表与通配名称,并指出诊断工具的边界及创建 ACL 时的安全步骤。

主要功能

  1. 01梳理记录级与字段级 ACL
  2. 02比较继承表和通配规则
  3. 03解释规则角色差异
  4. 04介绍 ACL 创建与检查步骤
  5. 05提供常见访问控制模式

适用场景

  1. 01排查用户无法访问记录或字段
  2. 02规划基于角色或所有权的访问
  3. 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_includeScript Include 访问用户能调用此 API 吗?
ui_pageUI Page 访问用户能查看此页面吗?
rest_endpointREST 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 APIAPI 会绕过 UI ACL创建专用的 REST ACL

评论

支持 Markdown