acl-security
$
npx mdskill add serac-labs/serac/acl-securityCreate and debug ServiceNow ACLs for row- and field-level security
- Solves access control issues for records, fields, REST, and script-include resources
- Uses Snow-Code tools like ACL review, table query, and impersonation testing
- Analyzes role/condition/script patterns and ACL evaluation order
- Provides visibility into field-level access and user impersonation results
SKILL.md
.github/skills/acl-securityView on GitHub ↗
---
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_review_access_control
- snow_query_table
- snow_find_artifact
- snow_execute_script_with_output
- 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):
1. **Table.field** - Most specific (e.g., `incident.assignment_group`)
2. \*_Table._` - Table-level field wildcard
3. **Table** - Table-level record ACL
4. **Parent table ACLs** - If table extends another
5. `*` - 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
```javascript
// Table-level READ ACL
snow_create_acl({
name: "incident",
operation: "read",
admin_overrides: true,
active: true,
roles: ["itil", "incident_manager"],
condition: "current.active == true",
script: "",
})
// Field-level WRITE ACL
snow_create_acl({
name: "incident.priority",
operation: "write",
roles: ["incident_manager"],
condition: "",
script: "answer = current.state < 6;", // Only if not resolved
})
```
## Common ACL Patterns
### Pattern 1: Role-Based Access
```javascript
// Condition: (empty - role check only)
// Roles: itil, incident_manager
// Script: (empty)
// Users with itil OR incident_manager role can access
```
### Pattern 2: Ownership-Based Access
```javascript
// Condition:
current.caller_id == gs.getUserID() || current.assigned_to == gs.getUserID() || current.opened_by == gs.getUserID()
// User can access their own records
```
### Pattern 3: Group-Based Access
```javascript
// Script:
;(function () {
var userGroups = gs.getUser().getMyGroups()
answer = userGroups.indexOf(current.assignment_group.toString()) >= 0
})()
// User can access records assigned to their groups
```
### Pattern 4: Manager Chain Access
```javascript
// 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
})()
```
### Pattern 5: Time-Based Access
```javascript
// 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
})()
```
### Pattern 6: Data Classification
```javascript
// 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]
})()
```
## Field-Level Security Patterns
### Hide Sensitive Fields
```javascript
// ACL: incident.u_ssn (Social Security Number)
// Operation: read
// Script:
answer = gs.hasRole("hr_admin")
// Only HR admins can see SSN field
```
### Read-Only After State Change
```javascript
// ACL: incident.short_description
// Operation: write
// Script:
answer = current.state < 6 // Can't edit after Resolved
// Prevent editing after resolution
```
### Conditional Field Visibility
```javascript
// ACL: incident.u_internal_notes
// Operation: read
// Condition:
gs.hasRole("itil") || current.caller_id == gs.getUserID()
// ITIL users see all, callers see their own
```
## Security Best Practices
### 1. Principle of Least Privilege
```javascript
// ❌ BAD - Too permissive
// Roles: (empty) - allows everyone
// ✅ GOOD - Explicit roles
// Roles: itil, incident_manager
```
### 2. Deny by Default
```javascript
// Create a catch-all deny ACL at lowest priority
// Name: *
// Operation: read
// Condition: false
// This ensures anything not explicitly allowed is denied
```
### 3. Avoid Complex Scripts
```javascript
// ❌ 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. Test ACLs Thoroughly
```javascript
// Use "Impersonate User" to test ACLs as different users
// Check: Navigation, List views, Forms, Related lists
// Verify: Fields hidden, buttons disabled, records filtered
```
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
```javascript
// 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 User Permissions
```javascript
// 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())
```
## 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 |
More from serac-labs/serac
- agent-workspaceBuild ServiceNow Agent Workspace configurations — workspaces, lists, forms, contextual side panels, Agent Assist similar-record finders, and workspace-specific UI actions on sys_aw_* tables.
- approval-workflowsConfigure ServiceNow approval rules and sysapproval_approver records — manager/group/script approvers, multi-level routing, delegation via sys_user_delegate, and parent-record state rollup.
- asset-managementManage ServiceNow hardware assets, software licenses, and lifecycle states on alm_hardware/alm_license — license allocation, CMDB-to-asset linking, warranty tracking, inventory aggregation (HAM/SAM).
- atf-testingBuild ServiceNow Automated Test Framework tests and suites — impersonation, form steps, assertions, server-side script steps, test parameters, and execution via snow_create_atf_test / snow_execute_atf_test.
- blast-radiusTrace ServiceNow configuration dependencies — what artifacts touch a given field, what calls a script include, table/app-level config inventory. Use before deletes, renames, or refactors.
- bun-file-ioUse this when you are working on file operations like reading, writing, scanning, or deleting files. It summarizes the preferred file APIs and patterns used in this repo. It also notes when to use filesystem helpers for directories.
- business-rule-patternsWrite ServiceNow business rules (before/after/async/display) — current vs previous, changesTo/changesFrom, recursion avoidance, setAbortAction, and async dispatch for heavy work.
- catalog-itemsBuild ServiceNow Service Catalog items, variables, variable sets, catalog client scripts, record producers, and order guides with reference qualifiers and dynamic pricing.
- client-scriptsWrite ServiceNow client scripts (onLoad/onChange/onSubmit/onCellEdit) using g_form, g_user, GlideAjax, field visibility/mandatory toggles, and validation with debounced server calls.
- cmdb-patternsCreate ServiceNow CIs and cmdb_rel_ci relationships, walk upstream/downstream impact, detect orphan/stale CIs, and align discovered CIs with the proper sys_class_name hierarchy.