Impersonation is one of those features that is necessary, dangerous, and uneven across instances in exactly the wrong way. Admins use it to debug user-reported issues. Auditors look at it and see “an admin became another user and did things.” If your audit trail cannot answer the auditor’s questions in under five minutes, the conversation is going to get uncomfortable.
What “impersonation” actually does on the platform
When an admin impersonates another user, the session becomes that user for the duration. ACLs, role-based access, scope visibility, and most platform behaviors evaluate as if the impersonated user logged in. The impersonating admin’s identity is recorded in the session metadata and in sys_audit_delete and sys_user_session, but day-to-day record audit fields (sys_created_by, sys_updated_by) record the impersonated user, not the admin.
This is the source of every audit-trail problem. A record updated under impersonation looks, in the record’s own audit fields, like the impersonated user did it. To trace back to the admin who actually did the work, you have to correlate session metadata.
The three questions an auditor will ask
Prepare for these three, in this order:
- Who impersonated whom, and when? A list of impersonation events with start time, end time, impersonating admin, and impersonated user.
- What did each impersonation session do? The record changes, scripts run, and configuration touched during each session.
- Was each impersonation justified? Documentation linking the session to a ticket, support request, or incident that authorized it.
The first is the platform’s job; ServiceNow logs the event. The second requires you to join audit data to session metadata; the platform does not do this for you out of the box. The third requires policy that the platform cannot enforce — only your runbook can.
Building the cross-reference
// Background script — list impersonation sessions in last 7 days
function impersonationReport(days) {
var since = new GlideDateTime();
since.addDaysLocalTime(-days);
var sess = new GlideRecord('sys_user_session');
sess.addQuery('impersonator', '!=', null);
sess.addQuery('sys_created_on', '>=', since.getValue());
sess.orderBy('sys_created_on');
sess.query();
while (sess.next()) {
var summary = {
session_id: sess.sys_id.toString(),
admin: sess.impersonator.getDisplayValue(),
user: sess.user.getDisplayValue(),
started: sess.sys_created_on.toString(),
ended: sess.sys_updated_on.toString()
};
gs.info(JSON.stringify(summary));
}
}
impersonationReport(7);
Pair that with an audit query joining against sys_audit for the impersonated user during the session window. The join is the bit nobody automates and everybody needs.
The change-during-impersonation report
// For a single impersonation session, list records modified during the window
function changesDuring(sessionSysId) {
var s = new GlideRecord('sys_user_session');
s.get(sessionSysId);
if (!s) return;
var aud = new GlideRecord('sys_audit');
aud.addQuery('user', s.user); // recorded as impersonated user
aud.addQuery('sys_created_on', '>=', s.sys_created_on.toString());
aud.addQuery('sys_created_on', '<=', s.sys_updated_on.toString());
aud.orderBy('sys_created_on');
aud.query();
var changes = [];
while (aud.next()) {
changes.push({
table: aud.tablename.toString(),
record: aud.documentkey.toString(),
field: aud.fieldname.toString(),
old: aud.oldvalue.toString().substring(0, 50),
new: aud.newvalue.toString().substring(0, 50),
when: aud.sys_created_on.toString()
});
}
return { admin: s.impersonator.getDisplayValue(), session: sessionSysId, changes: changes };
}
This is the report you hand to the auditor. Run it for the sample of sessions they ask about. Output: a clear list of “this admin, impersonating this user, modified these records during this window.”
Pre-impersonation ticket linkage
The platform cannot tell you whether an impersonation was justified. Your policy must. The discipline that works:
- Before initiating impersonation, the admin records a justification — usually a ticket number that requested the work.
- The justification is stored in a custom field on the session record (or in a side table).
- Impersonations without justification are flagged in the weekly review report.
// UI Action override — capture justification before impersonation
function impersonateWithJustification(targetUserSysId, ticketNumber, reason) {
if (!ticketNumber) {
gs.addErrorMessage('Ticket number required for impersonation');
return false;
}
var log = new GlideRecord('u_impersonation_justification');
log.initialize();
log.u_admin = gs.getUserID();
log.u_target_user = targetUserSysId;
log.u_ticket = ticketNumber;
log.u_reason = reason;
log.u_started_at = new GlideDateTime();
log.insert();
// Then proceed with standard impersonation
return true;
}
The friction is the point. If impersonation is one click, admins will use it casually. If it requires a ticket reference, admins will pause and either find the ticket or decide they did not need to impersonate after all.
What to restrict and what to allow
Not every admin role should be able to impersonate. The right pattern:
- A small group (security_admin or platform_admin) can impersonate any user.
- Service-desk leads can impersonate non-admin users for support purposes.
- Nobody should be able to impersonate users with elevated roles (admin, security_admin, system_administrator) without a heightened approval flow.
The platform supports finer-grained controls on impersonation than most teams use. Configure them. The default “any admin can impersonate any user” is a regulatory liability.
For the broader ACL discipline of which this is one piece, see our ACL deny-by-default pattern.
Time-boxed sessions
An impersonation session that runs for hours is almost always wrong. Either the admin forgot to end it, or the admin is doing far more than the support ticket warranted. The defense:
- Set a maximum session duration. 30 minutes is reasonable for typical debug work.
- Sessions approaching the limit get a banner warning.
- Sessions exceeding the limit are auto-terminated.
// Scheduled job — terminate stale impersonation sessions
var MAX_MINUTES = 30;
var stale = new GlideRecord('sys_user_session');
stale.addQuery('impersonator', '!=', null);
stale.addQuery('active', true);
var cutoff = new GlideDateTime();
cutoff.addSeconds(-MAX_MINUTES * 60);
stale.addQuery('sys_created_on', '<=', cutoff.getValue());
stale.query();
while (stale.next()) {
gs.warn('Terminating stale impersonation: ' + stale.sys_id);
stale.active = false;
stale.update();
}
The auto-terminate is annoying for admins doing legitimate long work. The annoyance is the protective signal — long work should be done as themselves, not under impersonation.
High-risk action detection
Some actions taken under impersonation deserve immediate attention regardless of the session’s other context:
- Changes to user records (especially role grants)
- Changes to ACL records
- Changes to integration credentials
- Bulk deletion of records
- Modifications to scheduled jobs or business rules
Build a Business Rule that watches for these classes of change and, when they occur under an active impersonation session, emits an alert to the security team. The signal-to-noise on this alert is usually high — most fire on real issues — because the combination is unusual.
The weekly review meeting
Once a week, the security team reviews the impersonation log. Five questions, in order:
- Any sessions without a justification?
- Any sessions longer than the time-box would have allowed?
- Any sessions that touched high-risk records?
- Any admin with anomalous frequency compared to their trailing average?
- Any impersonated user who was on PTO at the time?
The fifth is the one that catches the most surprising issues. An impersonation session active at 11pm for a user who was on vacation is, at minimum, worth a phone call to the admin who initiated it.
UI for the audit reviewer
The audit reviewer needs a one-stop view: impersonation sessions, filterable by date range, admin, and impersonated user, with the changes report one click away. The standard sys_audit list is not adequate — too generic, too noisy. Build a custom list filtered to impersonation context with the right default columns. Pin to the security dashboard.
Tradeoffs to be honest about
Tight impersonation policy creates friction. Admins doing legitimate debug work will sometimes feel slowed. The compensating benefit is that when an auditor or, worse, a regulator asks “show me what admin X did between 2pm and 5pm on Tuesday,” you have a clean answer. The answer is worth the friction.
The other honest tradeoff: many small organizations get away without this discipline for years. Then they grow or get acquired or get audited, and the discipline becomes a six-month retrofit project. Building the audit trail before you need it costs far less than retrofitting it after.
Key takeaways
- Impersonation records the impersonated user in record audit fields, not the admin. The trail to the admin requires joining session metadata to audit data.
- Three questions the auditor will ask: who did what, when, with what justification. Prepare reports for all three before the audit.
- Pre-impersonation justification (ticket number) is the friction that prevents casual use and the documentation auditors want.
- Time-box sessions to 30 minutes. Stale sessions are either forgotten or doing too much.
- High-risk actions under impersonation deserve a same-day alert, not a weekly summary.