Tools & ACL Security
AI In A Box supports function calling (tools) that allow the LLM to retrieve data and perform actions in ServiceNow. When enabled, Tools ACL Security ensures these tool calls respect the requesting user's access controls.
What Are Tools?
Tools are functions that the LLM can call during a conversation. For example:
- get_incident - Look up incident details by number or caller
- search_knowledge - Search knowledge base articles
- create_task - Create a new task record
When a user asks "What incidents has David Miller reported?", the LLM can call the get_incident tool to fetch real data instead of making up an answer.
Why ACL Security Matters
Without ACL security, tools run as the integration user (e.g., aiab_service), which typically has elevated permissions. This means:
- Users could access data they shouldn't see
- Field-level ACLs would be bypassed
- Sensitive information could be exposed
With ACL security enabled, tool calls are impersonated as the requesting user, ensuring all ServiceNow access controls are enforced.
How It Works
- 1. User asks a question in chat
- 2. LLM decides to call a tool (e.g.,
get_incident) - 3. AI In A Box server calls back to ServiceNow with the user's identity
- 4. ServiceNow impersonates the user before executing the tool script
- 5. Tool script uses
GlideRecordSecurewhich respects ACLs - 6. Only data the user can access is returned to the LLM
Setup Requirements
To enable Tools ACL Security, you need three components:
1. Service Account with Admin Role
Create a dedicated service account that will authenticate callbacks and perform impersonation:
- Go to User Administration > Users
- Create a new user (e.g.,
aiab_service) - Set a strong password
- Grant the admin role
GlideImpersonate API requires elevated privileges to work in REST API context. The impersonator role alone is not sufficient - testing has confirmed that only the admin role enables impersonation via REST callbacks. The service account will impersonate the requesting user before executing tool scripts, so the actual data access is governed by the requesting user's ACLs, not the service account's.
2. Callback Auth Property
Set the system property that stores the service account credentials:
ai_in_a_box.config.server.callback.auth = aiab_service:your-password
The AI In A Box server uses these credentials for Basic Authentication when calling back to ServiceNow to execute tools. ServiceNow's REST API authentication validates these credentials before any tool execution occurs.
3. Global Script Include
The AIABGlobalHelper script include must be installed in the global scope. You have two options:
Option A: Import the Update Set (Recommended)
Download and import the Global Helper update set from GitHub:
- Download AIABGlobalHelper Update Set from the releases page
- Go to System Update Sets > Retrieved Update Sets
- Click Import Update Set from XML
- Upload the XML file and click Preview
- Click Commit to install
Option B: Create Manually
Create a global scope script include called AIABGlobalHelper:
var AIABGlobalHelper = Class.create();
AIABGlobalHelper.prototype = {
initialize: function() {},
getUserName: function(userId) {
var sys_user = new GlideRecord("sys_user");
if (sys_user.get(userId)) {
return sys_user.getValue("user_name");
}
return "NO USER FOUND with userId " + userId;
},
/**
* Impersonate a user - callable from scoped apps
* @param {string} userId - sys_id of user to impersonate
* @returns {string|null} - original user sys_id if successful, null if failed
*/
impersonate: function(userId) {
if (!userId) {
gs.warn("AIABGlobalHelper.impersonate: No userId provided");
return null;
}
var originalUser = gs.getUserID();
var originalUserName = this.getUserName(originalUser);
gs.info("AIABGlobalHelper.impersonate: attempting " + userId + " from " + originalUser + "[" + originalUserName + "]");
try {
var impersonator = new GlideImpersonate();
impersonator.impersonate(userId);
var newUser = gs.getUserID();
var newUserName = this.getUserName(newUser);
gs.info("AIABGlobalHelper.impersonate: now running as " + newUser + "[" + newUserName + "]");
if (newUser === userId) {
gs.info("AIABGlobalHelper.impersonate: SUCCESS");
return originalUser;
} else {
gs.warn("AIABGlobalHelper.impersonate: FAILED - still " + newUser);
return null;
}
} catch (e) {
gs.error("AIABGlobalHelper.impersonate: EXCEPTION - " + e);
return null;
}
},
/**
* Revert impersonation to original user
* @param {string} originalUserId - sys_id of original user
*/
revert: function(originalUserId) {
if (originalUserId) {
try {
var impersonator = new GlideImpersonate();
impersonator.impersonate(originalUserId);
gs.info("AIABGlobalHelper.revert: reverted to " + originalUserId);
} catch (e) {
gs.warn("AIABGlobalHelper.revert failed: " + e);
}
}
},
type: "AIABGlobalHelper"
};
GlideImpersonate is only available in global scope. If you have a duplicate in the scoped AI In A Box app, disable it.
Writing ACL-Safe Tool Scripts
When creating tools, follow these best practices to ensure ACLs are respected:
Use GlideRecordSecure
Always use GlideRecordSecure instead of GlideRecord:
(function(args, userId) {
// GOOD: Uses GlideRecordSecure which respects ACLs
var gr = new GlideRecordSecure('incident');
if (gr.get('number', args.number)) {
return {
number: gr.getValue('number'),
short_description: gr.getValue('short_description'),
state: gr.getDisplayValue('state')
};
}
return { error: 'Incident not found' };
})(args, userId);
Check Field-Level Access
For sensitive fields, explicitly check read access:
(function(args, userId) {
var gr = new GlideRecordSecure('incident');
if (gr.get('number', args.number)) {
var result = {
number: gr.getValue('number'),
short_description: gr.getValue('short_description')
};
// Only include caller_id if user can read it
if (gr.caller_id.canRead()) {
result.caller_id = gr.getDisplayValue('caller_id');
}
return result;
}
return { error: 'Incident not found' };
})(args, userId);
Avoid ACL Bypasses
Never use:
gr.setWorkflow(false)to bypass business rulesGlideRecorddirectly (useGlideRecordSecurefor ACL enforcement)
Troubleshooting
"Impersonation failed" or "Still running as [service account]"
This is the most common issue. The GlideImpersonate API requires the admin role to work in REST API context:
- Verify the service account has the
adminrole (not justimpersonator) - Check the system logs for "AIABGlobalHelper.impersonate: FAILED" messages
- Confirm the user you're impersonating exists and is active
impersonator role is NOT sufficient for GlideImpersonate to work in REST API context. You need the admin role on the service account.
Tool Returns 401 Unauthorized
- Check
ai_in_a_box.config.server.callback.authcontains valid credentials - Verify the service account is active and not locked out
- Confirm the password is correct (format:
username:password)
Tool Returns No Data (But Should)
- The user may not have access to the record (ACL working correctly!)
- Check the user's roles and group memberships
- Review the table and field-level ACLs
"AIABGlobalHelper not found"
- Ensure the script include is in global scope (not the AI In A Box scoped app)
- If you have duplicates, disable the one in the scoped app
- Verify the name is exactly
AIABGlobalHelper - Check that the script include is active
Duplicate AIABGlobalHelper Script Includes
If you have AIABGlobalHelper in both global scope and the AI In A Box scoped app:
- Keep the global scope version active
- Disable the scoped app version
- ServiceNow's scope resolution may pick the wrong one if both are active
Example: get_incident Tool
Here's a complete example of an ACL-safe tool with multiple search options:
Parameters
{
"type": "object",
"properties": {
"number": {
"type": "string",
"description": "Incident number like INC0000001"
},
"caller": {
"type": "string",
"description": "Name of the person who reported the incident"
},
"assigned_to": {
"type": "string",
"description": "Name of the person assigned to the incident"
},
"assignment_group": {
"type": "string",
"description": "Name of the group assigned to the incident"
},
"state": {
"type": "string",
"enum": ["new", "in_progress", "on_hold", "resolved", "closed"],
"description": "Incident state"
},
"priority": {
"type": "string",
"enum": ["1", "2", "3", "4", "5"],
"description": "Priority level (1=Critical, 5=Planning)"
},
"opened_today": {
"type": "boolean",
"description": "If true, only return incidents opened today"
},
"short_description_contains": {
"type": "string",
"description": "Search for text in the short description"
},
"limit": {
"type": "integer",
"description": "Max number of incidents to return (default 5, max 10)"
}
},
"required": []
}
Script
(function(args, userId) {
// Impersonation is handled by the Execute Tool endpoint via AIABGlobalHelper
// Use GlideRecordSecure to respect ACLs of the impersonated user
var incidentRecord = new GlideRecordSecure("incident");
if (args.number) {
incidentRecord.addQuery("number", args.number);
}
if (args.caller) {
incidentRecord.addQuery("caller_id.name", "LIKE", args.caller);
}
if (args.assigned_to) {
incidentRecord.addQuery("assigned_to.name", "LIKE", args.assigned_to);
}
if (args.assignment_group) {
incidentRecord.addQuery("assignment_group", "LIKE", args.assignment_group);
}
if (args.state) {
var stateMap = {
"new": 1,
"in_progress": 2,
"on_hold": 3,
"resolved": 6,
"closed": 7
};
if (stateMap[args.state.toLowerCase()]) {
incidentRecord.addQuery("state", stateMap[args.state.toLowerCase()]);
}
}
if (args.priority) {
incidentRecord.addQuery("priority", args.priority);
}
if (args.opened_today === true) {
incidentRecord.addQuery("opened_at", ">=", gs.beginningOfToday());
}
if (args.short_description_contains) {
incidentRecord.addQuery("short_description", "CONTAINS", args.short_description_contains);
}
incidentRecord.orderByDesc("opened_at");
incidentRecord.setLimit(Math.min(args.limit || 5, 10));
incidentRecord.query();
var incidents = [];
while (incidentRecord.next()) {
var incident = {
number: "" + incidentRecord.number,
short_description: "" + incidentRecord.short_description,
state: incidentRecord.getDisplayValue("state"),
priority: incidentRecord.getDisplayValue("priority"),
assigned_to: incidentRecord.getDisplayValue("assigned_to"),
opened_at: "" + incidentRecord.opened_at
};
// Only include caller_id if user can read it
if (incidentRecord.caller_id.canRead()) {
incident.caller_id = incidentRecord.getDisplayValue("caller_id");
}
incidents.push(incident);
}
if (incidents.length === 0) {
return { found: false, count: 0, message: "No incidents found" };
}
// If searching by number and found exactly one, return full details
if (args.number && incidents.length === 1) {
incidentRecord.get("number", args.number);
var singleIncident = {
number: "" + incidentRecord.number,
short_description: "" + incidentRecord.short_description,
description: "" + incidentRecord.description,
state: incidentRecord.getDisplayValue("state"),
priority: incidentRecord.getDisplayValue("priority"),
assigned_to: incidentRecord.getDisplayValue("assigned_to"),
opened_at: "" + incidentRecord.opened_at
};
// Only include caller_id if user can read it
if (incidentRecord.caller_id.canRead()) {
singleIncident.caller_id = incidentRecord.getDisplayValue("caller_id");
}
return { found: true, incident: singleIncident };
}
return { found: true, count: incidents.length, incidents: incidents };
})(args, userId);
Security Considerations
Service Account Security
The service account has admin role, but this is mitigated by:
- The account only performs impersonation, then runs as the requesting user
- Actual data access is governed by the impersonated user's ACLs
- All tool executions are logged in the
u_ai_inferencetable - Credential storage is encrypted in ServiceNow properties
Best Practices
- Use a dedicated service account (not a real person's account)
- Set a strong, unique password
- Rotate the password periodically
- Monitor the
u_ai_inferencetable for unusual activity - Review tool scripts for ACL compliance before deployment
Audit Trail
All tool executions are logged in the u_ai_inference table, including:
- The requesting user (
u_requested_by) - Tools called and their arguments (
u_tool_calls) - Results returned to the LLM
Support
Need help setting up Tools ACL Security?
- Contact Support
- Schedule a call with our team