About acl-management
This skill guides ServiceNow administrators and security staff through inspecting, creating, modifying, and troubleshooting ACLs. It covers table-, field-, and record-level access controls, role requirements, and debugging methods.
Key features
- 01Query ACLs and their role requirements
- 02Create table-, field-, and record-level ACLs
- 03Modify existing ACLs safely
- 04Troubleshoot user permission issues
- 05Analyze ACL evaluation and conflicts
- 06Check for risky role combinations
Use cases
- 01Set up access controls for a new table or application
- 02Investigate user access-denied problems
- 03Implement field- or row-level permissions
- 04Review existing permissions and ACL configuration
MCP support
The skill uses ServiceNow MCP tools to query ACLs, create or update rules and roles, and run scripts to support debugging.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ happy-technologies-llc/ happy-platform-skills --skill acl-managementAfter installing, type/acl-managementin Claude Code to confirm it loaded.
Details
From the project READMEACL Management
Overview
Access Control Lists (ACLs) are the foundation of ServiceNow security. This skill covers:
- Understanding ACL structure and inheritance hierarchy
- Creating new ACLs for tables, fields, and records
- Modifying existing ACL rules safely
- Troubleshooting permission issues
- ACL debugging techniques and tools
- Best practices for role-based access control
When to use:
- Setting up security for new tables or applications
- Troubleshooting "access denied" issues
- Implementing field-level security
- Auditing and reviewing existing permissions
- Implementing row-level security (query conditions)
Prerequisites
- Roles:
security_adminoradmin - Access: sys_security_acl, sys_security_acl_role tables
- Knowledge: Understanding of ServiceNow roles and user groups
- Plugins: Core platform (ACLs are built-in)
ACL Fundamentals
ACL Hierarchy
┌─────────────────┐
│ Table ACL (*) │ ← Broadest
│ incident.* │
└────────┬────────┘
│
┌────────▼────────┐
│ Table.Field │ ← Field-specific
│ incident.state │
└────────┬────────┘
│
┌────────▼────────┐
│ Table.None │ ← Row-level
│ (with script) │
└─────────────────┘ ← Most specific
ACL Operations
| Operation | Purpose | Common Use |
|---|---|---|
read | View records/fields | List, form display |
write | Modify records/fields | Form editing |
create | Insert new records | New record creation |
delete | Remove records | Record deletion |
execute | Run scripts/actions | UI actions, processors |
ACL Evaluation Order
- Most specific wins: Field ACL > Table ACL
- First match: Evaluation stops at first matching ACL
- Deny by default: No matching ACL = access denied
- Admin bypass: Admin role bypasses most ACLs
Procedure
Phase 1: Understanding Existing ACLs
1.1 Query ACLs for a Table
Find all ACLs for a specific table:
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl
query: nameLIKEincident
fields: name,operation,type,condition,script,active
limit: 50
Find ACLs by operation type:
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl
query: name=incident^operation=write
fields: name,operation,admin_overrides,condition,script,active
limit: 20
1.2 Discover ACL Structure
Get the ACL table schema:
Tool: SN-Get-Table-Schema
Parameters:
table_name: sys_security_acl
Key ACL Fields:
| Field | Type | Purpose |
|---|---|---|
name | String | Table.field or Table.* |
operation | Choice | read, write, create, delete, execute |
type | Choice | record, client_callable_script_include |
condition | Condition | Filter conditions for row-level security |
script | Script | Advanced condition logic |
admin_overrides | Boolean | Allow admin bypass |
active | Boolean | ACL is enforced |
advanced | Boolean | Uses script conditions |
1.3 Check ACL Role Requirements
Find roles required for an ACL:
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl_role
query: acl.name=incident.*^acl.operation=write
fields: acl.name,acl.operation,role.name
limit: 20
Phase 2: Creating New ACLs
2.1 Create Table-Level ACL
Create read ACL for custom table:
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl
data:
name: "u_custom_table.*"
operation: read
type: record
admin_overrides: true
active: true
description: "Read access to custom table"
Add role requirement:
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl_role
data:
acl: [acl_sys_id]
role: [role_sys_id]
2.2 Create Field-Level ACL
Protect a sensitive field:
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl
data:
name: "u_custom_table.ssn_number"
operation: read
type: record
admin_overrides: true
active: true
description: "Restrict SSN field to authorized users only"
Add multiple role requirements:
Tool: SN-Query-Table
Parameters:
table_name: sys_user_role
query: nameINhr_admin,security_admin
fields: sys_id,name
limit: 10
Then for each role:
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl_role
data:
acl: [field_acl_sys_id]
role: [hr_admin_role_sys_id]
2.3 Create Row-Level ACL (Condition-Based)
Users can only see their own records:
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl
data:
name: "u_custom_table"
operation: read
type: record
condition: "opened_by=javascript:gs.getUserID()"
admin_overrides: true
active: true
description: "Users can only read their own records"
2.4 Create Script-Based ACL
Complex logic requiring script:
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl
data:
name: "u_custom_table"
operation: write
type: record
advanced: true
script: |
// Allow write if user is the owner OR is in the assigned group
var userId = gs.getUserID();
var userGroups = gs.getUser().getMyGroups();
if (current.owner == userId) {
answer = true;
} else if (userGroups.indexOf(current.assignment_group.toString()) >= 0) {
answer = true;
} else {
answer = false;
}
admin_overrides: true
active: true
description: "Allow write for owner or group members"
Phase 3: Modifying ACLs Safely
3.1 Deactivate Before Major Changes
Deactivate ACL temporarily:
Tool: SN-Update-Record
Parameters:
table_name: sys_security_acl
sys_id: [acl_sys_id]
data:
active: false
Make changes:
Tool: SN-Update-Record
Parameters:
table_name: sys_security_acl
sys_id: [acl_sys_id]
data:
condition: "state!=6^ORassigned_to=javascript:gs.getUserID()"
description: "Updated condition - users can access non-closed or own records"
Reactivate after testing:
Tool: SN-Update-Record
Parameters:
table_name: sys_security_acl
sys_id: [acl_sys_id]
data:
active: true
3.2 Add/Remove Role Requirements
Add a new role requirement:
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl_role
data:
acl: [acl_sys_id]
role: [new_role_sys_id]
Remove a role requirement:
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl_role
query: acl=[acl_sys_id]^role=[role_sys_id]
fields: sys_id
# Then delete the record using SN-Update-Record or background script
Phase 4: Troubleshooting Permission Issues
4.1 Identify the Blocking ACL
Enable ACL Debugging (Session-Based):
Tool: SN-Execute-Background-Script
Parameters:
script: |
// This enables debug output for the session
// Run manually: Navigate to User menu > Impersonate > Check security
gs.info('To debug ACLs:');
gs.info('1. Navigate to System Security > Debugging');
gs.info('2. Or add ?sysparm_security_debug=true to URL');
gs.info('3. Or impersonate user and check security diagnostics');
description: "ACL debugging instructions"
Check user's roles:
Tool: SN-Query-Table
Parameters:
table_name: sys_user_has_role
query: user=[user_sys_id]^role.name!=snc_internal
fields: role.name,granted_by,inherited
limit: 50
Check user's groups:
Tool: SN-Query-Table
Parameters:
table_name: sys_user_grmember
query: user=[user_sys_id]
fields: group.name,group.sys_id
limit: 50
4.2 Trace ACL Evaluation
Find which ACL applies:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var table = 'incident';
var operation = 'write';
var fieldName = 'state'; // or '*' for table-level
var acl = new GlideRecord('sys_security_acl');
acl.addQuery('name', 'CONTAINS', table);
acl.addQuery('operation', operation);
acl.addQuery('active', true);
acl.orderBy('name');
acl.query();
gs.info('ACLs for ' + table + '.' + fieldName + ' (' + operation + '):');
while (acl.next()) {
var roles = [];
var roleGr = new GlideRecord('sys_security_acl_role');
roleGr.addQuery('acl', acl.sys_id);
roleGr.query();
while (roleGr.next()) {
roles.push(roleGr.role.name.toString());
}
gs.info(acl.name + ' - Roles: [' + roles.join(', ') + '] - Condition: ' + acl.condition);
}
description: "Trace ACL evaluation for table/operation"
4.3 Test User Access
Check if user has required roles:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var userId = '[user_sys_id]';
var requiredRoles = ['itil', 'incident_manager'];
var user = new GlideRecord('sys_user');
user.get(userId);
gs.info('Checking roles for: ' + user.user_name);
for (var i = 0; i < requiredRoles.length; i++) {
var hasRole = new GlideRecord('sys_user_has_role');
hasRole.addQuery('user', userId);
hasRole.addQuery('role.name', requiredRoles[i]);
hasRole.query();
if (hasRole.hasNext()) {
gs.info(' HAS: ' + requiredRoles[i]);
} else {
gs.warn(' MISSING: ' + requiredRoles[i]);
}
}
description: "Check user role membership"
4.4 Common Permission Issues
Issue: User can see table but not specific records
# Check for row-level ACL conditions
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl
query: name=[table_name]^operation=read^conditionISNOTEMPTY
fields: name,condition,script
Issue: User can read but not write
# Compare read vs write ACLs
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl
query: nameLIKE[table_name]^operationINread,write
fields: name,operation,condition
limit: 20
Issue: Field is read-only unexpectedly
# Check field-level ACLs
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl
query: name=[table_name].[field_name]^operation=write
fields: name,operation,condition,active
Phase 5: ACL Debugging Tools
5.1 Security Diagnostic Tool
Run security diagnostic for a record:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var table = 'incident';
var sysId = '[record_sys_id]';
var userId = '[user_sys_id]';
// Impersonate and check access
var gr = new GlideRecord(table);
gr.get(sysId);
var canRead = gr.canRead();
var canWrite = gr.canWrite();
var canDelete = gr.canDelete();
gs.info('Security Diagnostic for ' + table + '/' + sysId);
gs.info(' Can Read: ' + canRead);
gs.info(' Can Write: ' + canWrite);
gs.info(' Can Delete: ' + canDelete);
// Check field-level access
var elements = ['state', 'priority', 'assigned_to'];
for (var i = 0; i < elements.length; i++) {
var field = elements[i];
gs.info(' Field ' + field + ' - canRead: ' + gr[field].canRead() +
', canWrite: ' + gr[field].canWrite());
}
description: "Run security diagnostic on record"
5.2 ACL Rule Analyzer
Analyze all ACLs for conflicts:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var table = 'incident';
var analysis = {
table_acls: [],
field_acls: [],
potential_conflicts: []
};
var acl = new GlideRecord('sys_security_acl');
acl.addQuery('name', 'STARTSWITH', table);
acl.addQuery('active', true);
acl.orderBy('name');
acl.query();
while (acl.next()) {
var aclInfo = {
name: acl.name.toString(),
operation: acl.operation.toString(),
has_condition: acl.condition.toString() !== '',
has_script: acl.advanced.toString() === 'true'
};
if (acl.name.toString().indexOf('.') === -1 ||
acl.name.toString().endsWith('.*')) {
analysis.table_acls.push(aclInfo);
} else {
analysis.field_acls.push(aclInfo);
}
}
// Check for potential issues
if (analysis.table_acls.length === 0) {
analysis.potential_conflicts.push('No table-level ACLs - may be too open or rely on inheritance');
}
gs.info('ACL Analysis for ' + table + ':\n' + JSON.stringify(analysis, null, 2));
description: "Analyze ACLs for conflicts and issues"
5.3 Role Elevation Check
Find users with potentially dangerous role combinations:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var dangerousCombos = [
['admin', 'maint'],
['security_admin', 'personalize_dictionary'],
['admin', 'soap']
];
var findings = [];
for (var i = 0; i < dangerousCombos.length; i++) {
var combo = dangerousCombos[i];
var users = new GlideRecord('sys_user');
users.addQuery('active', true);
users.query();
while (users.next()) {
var hasAll = true;
for (var j = 0; j < combo.length; j++) {
var roleCheck = new GlideRecord('sys_user_has_role');
roleCheck.addQuery('user', users.sys_id);
roleCheck.addQuery('role.name', combo[j]);
roleCheck.query();
if (!roleCheck.hasNext()) {
hasAll = false;
break;
}
}
if (hasAll) {
findings.push({
user: users.user_name.toString(),
roles: combo
});
}
}
}
if (findings.length > 0) {
gs.warn('Users with dangerous role combinations:');
gs.info(JSON.stringify(findings, null, 2));
} else {
gs.info('No dangerous role combinations found');
}
description: "Find users with dangerous role combinations"
Best Practices
Design Principles
- Principle of Least Privilege: Grant minimum access needed
- Role-Based Access: Use roles, not individual users
- Inherit When Possible: Use table-level ACLs as baseline
- Document Everything: Add descriptions to all ACLs
- Test Thoroughly: Verify with multiple user roles
Common Patterns
Pattern 1: Owner + Group Access
// Condition for owner or group member access
opened_by=javascript:gs.getUserID()^ORassignment_group.memberSOME=javascript:gs.getUserID()
Pattern 2: Hierarchical Access
// Managers can see their team's records
var mgr = new GlideRecord('sys_user');
mgr.addQuery('manager', gs.getUserID());
mgr.query();
var users = [gs.getUserID()];
while (mgr.next()) users.push(mgr.sys_id.toString());
answer = gs.getUser().getID() in users.join(',').split(',');
Pattern 3: Time-Based Access
// Only allow access during business hours
var now = new GlideDateTime();
var hour = now.getLocalTime().getHourOfDay();
answer = (hour >= 8 && hour < 18);
Tool Usage Summary
| Operation | MCP Tool | REST Endpoint |
|---|---|---|
| Query ACLs | SN-Query-Table | GET /sys_security_acl |
| Create ACL | SN-Create-Record | POST /sys_security_acl |
| Update ACL | SN-Update-Record | PATCH /sys_security_acl |
| Add Role | SN-Create-Record | POST /sys_security_acl_role |
| Debug ACL | SN-Execute-Background-Script | Via sys_trigger |
| Check Schema | SN-Get-Table-Schema | GET /sys_dictionary |
Troubleshooting
ACL Not Being Evaluated
Symptom: Changes to ACL have no effect Cause: ACL cache not cleared or ACL inactive Solution:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Clear ACL cache
GlideAccessControl.reset();
gs.info('ACL cache cleared');
description: "Clear ACL cache"
Admin Can't Access Record
Symptom: Admin role blocked from record Cause: ACL has admin_overrides=false Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl
query: name=[table]^admin_overrides=false
fields: name,operation,admin_overrides
Script ACL Always Fails
Symptom: Script-based ACL never grants access Cause: Script error or incorrect answer variable Solution:
- Verify script sets
answer = trueoranswer = false - Check for script errors in syslog
- Ensure
advanced = trueis set
Role Not Granting Access
Symptom: User has role but still denied Cause: ACL requires multiple roles (AND) or condition fails Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_security_acl_role
query: acl=[acl_sys_id]
fields: role.name
All listed roles are required (AND logic by default).
Examples
Example 1: Secure a Custom Table
# Step 1: Create table-level read ACL
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl
data:
name: "u_employee_data.*"
operation: read
type: record
admin_overrides: true
active: true
description: "Base read access to employee data"
# Step 2: Add role requirement
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl_role
data:
acl: [step1_sys_id]
role: [hr_user_role_sys_id]
# Step 3: Create write ACL (more restrictive)
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl
data:
name: "u_employee_data.*"
operation: write
type: record
admin_overrides: true
active: true
description: "Write access to employee data - HR admins only"
# Step 4: Add admin role requirement
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl_role
data:
acl: [step3_sys_id]
role: [hr_admin_role_sys_id]
Example 2: Field-Level SSN Protection
# Protect SSN field - only security cleared users
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl
data:
name: "u_employee_data.ssn"
operation: read
type: record
admin_overrides: false
active: true
description: "SSN access restricted - security clearance required"
# Add special role
Tool: SN-Create-Record
Parameters:
table_name: sys_security_acl_role
data:
acl: [ssn_acl_sys_id]
role: [security_cleared_role_sys_id]
Related Skills
security/audit-compliance- Auditing ACL changessecurity/incident-response- Responding to access violationssecurity/data-classification- Identifying what needs protectionadmin/user-provisioning- Managing user roles