Why does gs.hasRole() behave differently in Background Script vs Scripted REST ACL?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday - last edited 6 hours ago
Hello ServiceNow Community 👋,
I've come across an interesting platform behavior while securing a Scripted REST API, and I'm curious to know whether this is expected by design or if there's something I'm overlooking.
I've spent some time debugging this and have been able to reproduce the behavior consistently. Before opening a support case, I wanted to check with the community to see if anyone has experienced something similar or can explain what's happening behind the scenes.
The Scenario
I created a custom role with the following details:
Role Name: Disaster Intake Agent
Role Label: Disaster Intake Agent
This role is assigned directly to the integration user that authenticates my inbound Scripted REST API.
Experiment #1 - Background Script
To verify the role assignment, I executed the following Background Script:
var gr = new GlideRecord('sys_user_role');
gr.addQuery('name', 'Disaster Intake Agent');
gr.query();
while (gr.next()) {
gs.info("Role Name : " + gr.name);
gs.info("Role Label : " + gr.getDisplayValue());
gs.info("gs.hasRole('Disaster Intake Agent') : " +
gs.hasRole("Disaster Intake Agent"));
gs.info("gs.hasRole('disaster_intake_agent') : " +
gs.hasRole("disaster_intake_agent"));
}Result
Both statements return true.
gs.hasRole("Disaster Intake Agent") -> true
gs.hasRole("disaster_intake_agent") -> trueAt this point everything looked perfectly normal.
Experiment #2 - Scripted REST External Default ACL
Next, I used exactly the same role checks inside the Scripted REST External Default ACL.
Test A
answer = !gs.hasRole("Disaster Intake Agent");This evaluates to true, meaning:
gs.hasRole("Disaster Intake Agent")returns false in the ACL.
Test B
answer = !gs.hasRole("disaster_intake_agent");This evaluates to false, meaning:
gs.hasRole("disaster_intake_agent")returns true.
Here's What Confused Me 🤔
For the same authenticated integration user, I get two different behaviors depending on where the code executes.
| Execution Context | Disaster Intake Agent | disaster_intake_agent |
| Background Script | ✅True | ✅True |
| Scripted REST External Default ACL | ❌False | ✅True |
The user, the assigned role, and the API request are all the same.
The only thing that changes is the execution context.
What I've Already Verified
✅The integration user is authenticated successfully.
✅The custom role is assigned directly to the user.
✅The behavior is reproducible every time.
✅Using the underscore version (disaster_intake_agent) works correctly inside the ACL.
Questions for the Experts
I'm trying to understand the platform internals rather than just finding a workaround.
Is this expected behavior for the Scripted REST External Default ACL?
Does the ACL engine resolve roles differently from other server-side execution contexts?
Why does gs.hasRole() successfully recognize both formats in a Background Script but only the internal role identifier inside the ACL?
Is the ACL evaluation engine bypassing any role alias or display-name resolution?
Is this behavior documented anywhere?
Has anyone else observed this while working with Scripted REST APIs or external integrations?
Should we always use the internal role name (underscore format) when calling gs.hasRole() inside ACLs, even if other server-side scripts accept both formats?
My Goal
I'm not looking for an alternative implementation because using the underscore version solves the problem.
What I'm really interested in is understanding why the same ServiceNow API (gs.hasRole()) behaves differently depending on where it's executed.
Is this:
Expected platform behavior?
A security optimization within the ACL engine?
A known limitation?
Or possibly something worth reporting?
I'd really appreciate insights from anyone who has explored the internals of ACL evaluation or role resolution.
Looking forward to hearing your thoughts. Thanks in advance! 🙏
#ServiceNow#ScriptedRESTAPI#AccessControl#ACL#gsHasRole#RESTAPI#Security#Authentication#Scripting#GlideSystem#Integration#Developer#ServerSideScripting