| name | acl-security |
| description | Create and debug ServiceNow ACLs (record, field, REST, script-include). Covers role/condition/script patterns, evaluation order, field-level visibility, and impersonation testing for row- and field-level security. |
| license | Apache-2.0 |
| compatibility | Designed for Snow-Code and ServiceNow development |
| metadata | {"author":"serac","version":"1.0.0","category":"servicenow"} |
| tools | ["snow_query_table","snow_test_acl","snow_artifact_manage","snow_execute_script","snow_impersonate_user","snow_session_context"] |
ACL Security Patterns for ServiceNow
Access Control Lists (ACLs) are the foundation of ServiceNow security. They control who can read, write, create, and delete records.
ACL Evaluation Order
ACLs are evaluated in this order (first match wins):
- Table.field - Most specific (e.g.,
incident.assignment_group)
- *Table.` - Table-level field wildcard
- Table - Table-level record ACL
- Parent table ACLs - If table extends another
* - Global wildcard (catch-all)
ACL Types
| Type | Controls | Example |
|---|
| record | Row-level access | Can user see this incident? |
| field | Field-level access | Can user see assignment_group? |
| client_callable_script_include | Script Include access | Can user call this API? |
| ui_page | UI Page access | Can user view this page? |
| rest_endpoint | REST API access | Can user call this endpoint? |
Creating ACLs via MCP
snow_create_acl({
name: "incident",
operation: "read",
admin_overrides: true,
active: true,
roles: ["itil", "incident_manager"],
condition: "current.active == true",
script: "",
})
snow_create_acl({
name: "incident.priority",
operation: "write",
roles: ["incident_manager"],
condition: "",
script: "answer = current.state < 6;",
})
Common ACL Patterns
Pattern 1: Role-Based Access
Pattern 2: Ownership-Based Access
current.caller_id == gs.getUserID() || current.assigned_to == gs.getUserID() || current.opened_by == gs.getUserID()
Pattern 3: Group-Based Access
;(function () {
var userGroups = gs.getUser().getMyGroups()
answer = userGroups.indexOf(current.assignment_group.toString()) >= 0
})()
Pattern 4: Manager Chain Access
;(function () {
var callerManager = current.caller_id.manager
var currentUser = gs.getUserID()
while (callerManager && !callerManager.nil()) {
if (callerManager.toString() == currentUser) {
answer = true
return
}
callerManager = callerManager.manager
}
answer = false
})()
Pattern 5: Time-Based Access
;(function () {
var now = new GlideDateTime()
var hour = parseInt(now.getLocalTime().getHourOfDayLocalTime())
answer = hour >= 8 && hour < 18
})()
Pattern 6: Data Classification
;(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]
})()
Field-Level Security Patterns
Hide Sensitive Fields
answer = gs.hasRole("hr_admin")
Read-Only After State Change
answer = current.state < 6
Conditional Field Visibility
gs.hasRole("itil") || current.caller_id == gs.getUserID()
Security Best Practices
1. Principle of Least Privilege
2. Deny by Default
3. Avoid Complex Scripts
;(function () {
var gr = new GlideRecord("sys_user_grmember")
gr.addQuery("user", gs.getUserID())
gr.query()
while (gr.next()) {
}
})()
4. Test ACLs Thoroughly
Use snow_impersonate_user to generate an audited impersonation deep-link (admin-only, writes to ~/.serac/audit/impersonations.jsonl). Use snow_session_context to confirm the caller's current roles and update set before diagnosing an ACL failure.
Debug ACLs
Enable ACL Debugging
gs.setProperty("glide.security.debug", "true")
gs.log("ACL Debug enabled")
Check User Permissions
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())
gs.info("Can read assignment_group: " + gr.assignment_group.canRead())
gs.info("Can write assignment_group: " + gr.assignment_group.canWrite())
Common Mistakes
| Mistake | Problem | Solution |
|---|
| No ACLs on custom tables | Anyone can access | Create ACLs immediately |
| Only role-based ACLs | No row-level security | Add conditions for data segregation |
| Scripts that query DB | Performance issues | Use conditions or cache results |
| Testing only as admin | Admin bypasses ACLs | Test as actual end users |
| Forgetting REST APIs | APIs bypass UI ACLs | Create specific REST ACLs |