Auditors do not read ACLs. They read symptoms — the wrong group reading the wrong record. The fix usually comes back to the same misunderstanding: ACLs do not AND, they OR within a tier and AND across tiers. Every drift story I have ever investigated starts here.
The model in one paragraph
For a given operation on a record, ServiceNow evaluates ACLs at three tiers — table, field, and table.field. Within a tier, any matching ACL granting access wins. Across tiers, all must grant. Add a permissive table-level ACL and you have just unlocked everything no field-level ACL explicitly forbids.
Where the OR kills you
A vendor app drops in this ACL on incident:
Table: incident
Operation: read
Roles: itil
Condition: <empty>
Script: answer = true;
Now any other read ACL on incident is irrelevant for ITIL users. The assigned_to=javascript:gs.getUserID() ACL you added last quarter? Bypassed.
Three rules to live by
- Never write
answer = trueunconditionally. If the script always returns true, you have built a permission bypass. - Field ACLs cannot make a record visible. They can only further restrict. Stop trying to “hide a record” with a field ACL.
- Roles on an ACL are an OR. Putting
itilplusadminmeans either; not both.
The audit query
Find every ACL where the script body trivially evaluates to true:
var gr = new GlideRecord('sys_security_acl');
gr.addQuery('script', 'CONTAINS', 'answer = true');
gr.addQuery('condition', '');
gr.query();
Every row is a candidate for a real condition.
Use the ACL Debugger before the auditor does
Impersonate a representative user from each business unit, hit the record, and turn on the ACL debug log. The output names the winning ACL — usually a global one no one remembers writing.
What to do this week
Run the audit query above and review the top 25 hits. Replace each answer = true with an explicit condition or delete the ACL. Re-impersonate. The right people lose access, and that is the point.