GlideEvaluator Dynamic Evaluation Detector
This page describes the
nowisor-glide-evaluator-detectorcheck from the nowisor Instance Scan Pack v1.0.0. The check is a LinterCheck — it walks every server-side script and emits one finding per reference toGlideEvaluator. A typical instance produces moderate volume —GlideEvaluatoris more common thaneval()in custom code because developers reach for it thinking it's "the ServiceNow way" to evaluate expressions.
What this finding means
Every finding identifies one line of server-side code that references the GlideEvaluator class. The check uses a deliberately broad AST predicate: it matches any NAME node whose identifier is GlideEvaluator, with no constraint on the parent. That catches all the relevant shapes:
new GlideEvaluator()— direct constructionGlideEvaluator.evaluateString(expr)— static-style callvar ev = GlideEvaluator;— bare reference (still suspicious enough to flag)
GlideEvaluator is ServiceNow's platform API for evaluating arbitrary JavaScript strings at runtime. The two main entry points — the constructor and evaluateString() — both take a string and execute it as JavaScript with full GlideRecord API access and the privileges of the calling script. Functionally, it is eval() with a ServiceNow-shaped namespace.
The most common developer misconception about GlideEvaluator is that it is "the safer ServiceNow way" to do dynamic evaluation. That is not true. The scope and privilege restrictions of GlideEvaluator are exactly the scope and privilege restrictions of the surrounding script — which, in a server-side context, are typically system-level. Any user-controlled string passed to GlideEvaluator becomes server-side code execution with no ACL boundary.
The check's finding volume is typically moderate — higher than eval() because legacy ServiceNow code reached for GlideEvaluator for "configurable expression" patterns in approvals, routing rules, and workflow conditions. Each occurrence in your scan output is a runtime code-execution sink that deserves individual review.
Why it matters for your compliance
GlideEvaluator carries the same compliance weight as eval(). Regulators don't distinguish between them; both are dynamic-evaluation patterns in the secure-coding control surface.
NIS2 Article 21§2(d) — supply chain security including security-related aspects concerning the relationships between each entity and its direct suppliers. Article 21§2(d) covers code shipping into the entity's environment from suppliers and internal teams. GlideEvaluator usage frequently traces to vendor-provided integrations or older platform examples that demonstrated "dynamic dispatch" patterns. NIS2 expects the entity to detect such artifacts and require their removal as part of supplier hygiene.
ISO 27001 A.8.28 — secure coding. The 2022 update made A.8.28 explicit about dangerous language constructs. GlideEvaluator is the platform-specific instance of the dynamic-eval pattern; ISO auditors who understand the platform will treat it equivalently to eval() for control purposes.
The check does not carry a DORA mapping in the manifest. The reason is editorial — DORA's relevant controls (Article 9§4(b) data integrity) route through the dynamic-evaluation findings cluster collectively rather than mapping to each individual function. The lack of an explicit mapping does not mean the check is irrelevant to DORA-regulated entities; it means the evidence pathway runs through the parent finding cluster.
Severity 1 (critical), like eval(). Same asymmetry: low cost to refactor, unbounded cost to leave in place if any input path reaches it.
The attack path
The path closely mirrors the eval() injection chain. The difference is the syntax of the sink, not the substance.
Step 1 — Find a reachable GlideEvaluator. The attacker enumerates server-side scripts looking for GlideEvaluator references where the input string flows from a controllable source. Common patterns:
- Approval routing scripts. A Service Catalog approval workflow evaluates a routing expression stored in a configuration record. Attacker with edit access to the configuration record (
itilor higher in many deployments) modifies the expression. - Workflow condition records. A workflow process evaluates a transition condition from a
wf_conditionrecord. Edit access to the workflow allows injection. - Reporting "computed field" definitions. A scheduled report calls
GlideEvaluatoron a formula stored with the report definition. Report owners can edit the formula.
Step 2 — Inject payload. The attacker writes JavaScript that does the privileged action they want. For instance, to exfiltrate every HR case to a publicly-readable table:
var src = new GlideRecord('sn_hr_core_case');
src.query();
while (src.next()) {
var copy = new GlideRecord('u_attacker_public_dump');
copy.initialize();
copy.setValue('content', src.getValue('description'));
copy.setWorkflow(false);
copy.insert();
}
(The setWorkflow(false) pairs with the nowisor-set-workflow-false-detector finding to silence the audit trail.)
Step 3 — Submit. The attacker stores the payload in the configuration record (workflow definition, approval routing, report formula). The next time the platform evaluates the expression — which happens automatically on whatever schedule the configuration drives — the payload executes.
Step 4 — Operate. The payload runs with the script's privileges (system in most contexts), reads or writes whatever the attacker chose, and the operation is invisible to ACL-based observation.
The defense is structural: remove GlideEvaluator from any code path reachable from a configuration record the attacker can edit. In practice, that means removing it from approval routing, workflow conditions, and report formulas — and migrating those features to a constrained expression language or a fixed dispatch table.
How to fix it
This is a triage check. Each finding needs review and a per-call decision.
For each finding, classify the input source:
Static literal argument (
GlideEvaluator.evaluateString('1+1')) — Refactor to direct logic. The dynamic evaluation has no benefit when the input is hardcoded.Input from a sys_properties value — Properties are editable by
adminand sometimesproperty_admin. A property whose value flows intoGlideEvaluatoris a privilege-escalation primitive for anyone with property edit access. Refactor: replace the property with a structured choice (achoice_listvalue) or a function-name reference into a static dispatch table.Input from a record field (workflow condition, approval routing, report formula) — Highest priority. Anyone with edit access to that record can inject code. Replace with a constrained expression language (a DSL that only supports field comparisons and boolean operators) or a fixed set of named operations selected from a choice list.
Input from a request parameter or form field — Critical. Anyone who can submit the request can inject code. Remove the dynamic evaluation entirely; the legitimate use case is essentially zero.
Legitimate platform-bootstrap context — Rare but possible (e.g., initial scope setup that needs to evaluate a snippet provided in an installation manifest). Verify the bootstrap is one-time, runs only as system, and is not reachable post-deployment. Add inline documentation.
The end state is "no GlideEvaluator references in customer-authored code." OOB platform usage may persist; that is platform vendor territory.
How to verify the fix
/*
* Verify nowisor-glide-evaluator-detector remediation progress
* Read-only, safe for production.
* Lists open findings grouped by source script.
*/
(function pullGlideEvaluatorFindings() {
var byScript = {};
var f = new GlideRecord('scan_finding');
f.addQuery('check.sys_scope.scope', 'x_nowisor_isp');
f.addQuery('check.name', 'GlideEvaluator Dynamic Evaluation Detector');
f.query();
while (f.next()) {
var sourceName = f.getDisplayValue('source') || '(unknown)';
if (!byScript[sourceName]) byScript[sourceName] = 0;
byScript[sourceName]++;
}
var names = Object.keys(byScript).sort();
gs.print('=== GlideEvaluator findings by source ===');
gs.print('Total scripts with findings: ' + names.length);
gs.print('');
for (var i = 0; i < names.length; i++) {
gs.print(' ' + names[i] + ': ' + byScript[names[i]] + ' reference(s)');
}
if (names.length === 0) {
gs.print('Clean: no GlideEvaluator references detected in current scan.');
}
})();
Track the count over time. A successful remediation campaign drops the count monotonically as scripts replace GlideEvaluator with constrained logic.
For an individual remediated script, the verification is: open the script record, search for GlideEvaluator, confirm zero matches. If any remain (legitimate bootstrap context), confirm they are accompanied by inline documentation explaining the exception.
What to do next
GlideEvaluator belongs to the dynamic-evaluation cluster. Closing it without closing the others leaves attacker-equivalent paths open:
nowisor-eval-usage-detector— directeval()calls, the same risk class with a different name.nowisor-set-workflow-false-detector— pairs with dynamic eval in the AP-007 chain. Eval gets the attacker into code execution;setWorkflow(false)makes the post-compromise action invisible.nowisor-set-roles-detector— privilege mutation. Dynamic eval that callssetRoles()is the canonical privilege-escalation primitive.nowisor-glide-record-vs-secure— everyGlideRecordcall inside evaluated code runs as SYSTEM regardless of ACLs.
For an entity migrating away from GlideEvaluator, the nowisor advisor product is being built to surface a "configuration-as-code" map showing every record (workflow, approval definition, report formula) whose stored value flows into a GlideEvaluator call — so your refactor effort can prioritize the records that pose the highest injection risk. Audit your dynamic-eval surface →