skills/security/acl-management/SKILL.md
Complete access control list management - understanding ACL structure, creating/modifying ACLs, troubleshooting permission issues, and debugging techniques
npx skillsauth add happy-technologies-llc/happy-servicenow-skills acl-managementInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Access Control Lists (ACLs) are the foundation of ServiceNow security. This skill covers:
When to use:
security_admin or admin ┌─────────────────┐
│ Table ACL (*) │ ← Broadest
│ incident.* │
└────────┬────────┘
│
┌────────▼────────┐
│ Table.Field │ ← Field-specific
│ incident.state │
└────────┬────────┘
│
┌────────▼────────┐
│ Table.None │ ← Row-level
│ (with script) │
└─────────────────┘ ← Most specific
| 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 |
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
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 |
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
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]
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]
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"
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"
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
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
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
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"
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"
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
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"
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"
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"
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);
| 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 |
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"
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
Symptom: Script-based ACL never grants access Cause: Script error or incorrect answer variable Solution:
answer = true or answer = falseadvanced = true is setSymptom: 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).
# 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]
# 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]
security/audit-compliance - Auditing ACL changessecurity/incident-response - Responding to access violationssecurity/data-classification - Identifying what needs protectionadmin/role-management - Managing user rolestesting
Manage supplier onboarding, qualification, performance monitoring, and offboarding with auditable lifecycle controls
tools
Identify emerging risks, prioritize intake signals, and route candidates into formal GRC risk assessment workflows
documentation
Screen inbound documents for completeness, policy risk, and routing readiness before extraction or case workflows
testing
Generate concise task summaries with status, timeline, blockers, SLA risk, and recommended next actions