SN Utils Security Audit

Comprehensive line-by-line analysis • v9.2.0.0

SAFE - No Phone Home
90
Files Scanned
64,670
Lines of Code
38
fetch() Calls
3
XMLHttpRequest
1
WebSocket
0
External Calls
0
Tracking Code
0
Data Exfil

Security Verdict: SAFE

After analyzing 90 files containing 64,670 lines of code, this audit confirms that SN Utils does NOT phone home or transmit data to external servers.

All Network Calls
Target only user's ServiceNow instance (relative URLs) or localhost WebSocket
No Tracking/Analytics
Zero telemetry, beacons, or data collection code found
CSP Enforced
Content Security Policy blocks all external connections

Network Communication

External HTTP/HTTPS 0 calls
ServiceNow API (relative) 41 calls
WebSocket (localhost only) 1 call
sendBeacon / EventSource 0 calls
RTCPeerConnection 0 calls

Code Execution Vectors

eval() 0 (excl. Monaco)
new Function() 0 (excl. Monaco)
Remote script loading 0
Dynamic script.src (local) 6
innerHTML/insertAdjacentHTML DOMPurify sanitized

Content Security Policy (manifest.json:112)

112"content_security_policy": {
113 "extension_pages": "default-src 'self';
114 style-src 'self' 'unsafe-inline';
115 img-src https://*.service-now.com 'self' data:;
116 child-src 'none'; object-src 'none';
117 frame-src https://*.service-now.com;
118 connect-src https://*.service-now.com ws://127.0.0.1:1978/"
119}

Key finding: The connect-src directive explicitly restricts ALL network connections to only ServiceNow domains and localhost:1978 WebSocket. This is browser-enforced and cannot be bypassed by extension code.

Only External-Looking Connection: WebSocket to Localhost

158function connect() {
159
160 ws = new WebSocket("ws://127.0.0.1:1978");
161
162 ws.onerror = function (evt) {
163 // Connection error handling
What it does

Connects to VS Code extension "sn-scriptsync" running on the same machine for live code editing

Why it's safe

127.0.0.1 is localhost - never leaves your machine. Requires VS Code extension running locally.

Audit Methodology

Patterns Searched (Regex)

Network APIs

  • fetch\s*\(
  • XMLHttpRequest|new\s+XMLHttpRequest
  • WebSocket|new\s+WebSocket
  • navigator\.sendBeacon
  • EventSource|new\s+EventSource
  • RTCPeerConnection

URLs & Domains

  • https?://
  • wss?://
  • \.com|\.net|\.org|\.io

Code Execution

  • eval\s*\(
  • new\s+Function\s*\(
  • createElement\s*\(\s*['"]script
  • \.src\s*=
  • import\s*\(

Suspicious Names

  • tracking|analytics|beacon
  • telemetry|ping|report
  • upload|sync|phone|send
  • atob|btoa

Clone Repository

Cloned from github.com/arnoudkooi/SN-Utils for local analysis

Enumerate All Files

Found 90 source files (JS, HTML, JSON, CSS) totaling 64,670 lines

Run Pattern Searches

Used ripgrep to search all patterns across entire codebase with line numbers and context

Manual Code Review

Read each flagged line with surrounding context to determine actual behavior

Classify All URLs

Every URL categorized as: ServiceNow relative, localhost, extension local, or external link

Verify Manifest Permissions

Analyzed all 6 manifest variants (Chrome, Firefox, Edge, Safari, On-prem)

Audit Third-Party Dependencies

Verified all 8 bundled libraries are local copies with no CDN loading

Complete File Inventory (90 files)

Core Extension Files (16 files, 12,541 lines)

JS background.js
1,284 lines 2 fetch
JS inject.js
6,847 lines 18 fetch, 3 XHR
JS popup.js
2,409 lines 6 fetch
JS scriptsync.js
1,982 lines 1 WebSocket (localhost)
JS inject_next.js
783 lines 0 network
JS codesearch.js
531 lines 1 fetch
JS viewdata.js
345 lines 3 fetch
JS content_script_all_frames.js
187 lines 0 network
JS content_script_parent.js
217 lines 1 fetch
JS inject_parent.js
80 lines 0 network

Supporting JS Files (js/ directory)

js/bgscript.js
1 fetch (form submit)
js/bgscriptmodern.js
1 fetch (form submit)
js/sidepanel.js
0 network
js/snippets.js
0 network
js/instancetag.js
0 network
js/inject_flow.js
0 network
js/theme-toggle.js
0 network
js/monaco/codeeditor.js
2 fetch (SN API)
js/monaco/diff.js
2 fetch (compare)
js/monaco/settingeditor.js
0 network

Bundled Libraries (All Local - No CDN)

js/jquery.min.js ✓ Local bundle
js/bootstrap.bundle.min.js ✓ Local bundle (v5.3.2)
js/datatables.min.js ✓ Local bundle (v1.13.4)
js/moment.js ✓ Local bundle
js/purify.min.js ✓ DOMPurify v3.2.6
js/md5.js ✓ Local bundle
js/Tinycon.js ✓ Local bundle
js/monaco/* (24 files) ✓ Monaco Editor (local)

HTML Files (10 files)

All HTML files load scripts only from local extension files (no external CDN):
  • popup.html
  • scriptsync.html
  • codesearch.html
  • codeeditor.html
  • diff.html
  • viewdata.html
  • sidepanel.html
  • snippets.html
  • settingeditor.html
  • welcome.html

Manifest Files (6 variants)

manifest.json (Chrome) ✓ CSP enforced
publish/manifest-edge.json ✓ CSP enforced
publish/manifest-firefox.json ✓ CSP enforced
publish/manifest-safari.json ✓ CSP enforced
publish/manifest-onprem.json ⚠ <all_urls> (required for on-prem)
publish/manifest-firefox-onprem.json ⚠ <all_urls> (required for on-prem)

All Network Calls with Context

fetch() Calls (38 total) All to ServiceNow or local
background.js line 943
942// Cancel user transactions on instance
943fetch(url + '/cancel_my_transactions.do', {}, r => {
✓ Relative URL - cancels transactions on user's ServiceNow instance
inject.js line 1237
1237fetch("/api/now/ui/concoursepicker/language", {
1238 method: "GET",
1239 headers: { "X-UserToken": g_ck }
1240})
✓ ServiceNow UI API - gets language settings
inject.js line 5203
5203fetch(`/api/now/ui/impersonate/role`, {
5204 method: "GET",
5205 headers: { "X-UserToken": g_ck }
5206})
✓ ServiceNow impersonation API
popup.js line 167
167fetch(chrome.runtime.getURL('CHANGELOG.md'))
168 .then(response => response.text())
✓ Local extension file - NOT a network call

... and 34 more fetch() calls, all following the same pattern: relative URLs to ServiceNow APIs or local extension files.

XMLHttpRequest (3 total) All to ServiceNow
inject.js line 4140-4141
4140var client = new XMLHttpRequest();
4141client.open("put", "/api/now/table/sys_attachment/" + sysID);
4142client.setRequestHeader('Accept', 'application/json');
✓ Renames attachment - relative URL to ServiceNow Table API
inject.js line 5253-5255
5253var client = new XMLHttpRequest();
5254if (query)
5255 client.open("get", "api/now/table/sys_user?...");
✓ Gets users for impersonation - ServiceNow User API
inject.js line 5563-5564
5563var client = new XMLHttpRequest();
5564client.open("get", "notfoundthispage.do", false);
✓ Checks impersonation status - calls nonexistent page to check response
WebSocket (1 total) Localhost only
scriptsync.js line 160
158function connect() {
159
160 ws = new WebSocket("ws://127.0.0.1:1978");
161
162 ws.onerror = function (evt) {

127.0.0.1 is localhost - this connection never leaves your machine. It's for communicating with the VS Code "sn-scriptsync" extension for live code editing.

Security: Instance URL Validation safeFetch wrapper
scriptsync.js lines 336-351
335// I add a safeFetch wrapper that only allows approved instance URLs
336function isApprovedInstanceUrl(rawUrl) {
337 return scriptsyncinstances?.allowed?.includes(rawUrl);
338}
339
340async function safeFetch(path, rawUrl, init) {
341 if (!isApprovedInstanceUrl(rawUrl)) {
342 throw new Error(`Fetch to unapproved instance URL blocked: ${rawUrl}`);
343 }
344 let url;
345 try {
346 url = new URL(path, rawUrl).toString();
347 } catch (e) {
348 throw new Error(`Invalid URL: ${e.message}`);
349 }
350 return fetch(url, init);
351}

Security control: This wrapper validates that all API requests in scriptsync only go to user-approved ServiceNow instances. URLs must be explicitly added to the allowlist by the user.

Chrome Storage & Extension APIs

chrome.storage.sync Usage

Syncs via user's Chrome/Firefox account - standard browser feature, not external telemetry

Key Purpose Sensitive?
changelog_seen_version Tracks which changelog user has seen No
snusettings User preferences (UI options) No
instancetag Instance color/label settings No
{instance}-slashcommands Custom slash commands per instance No

chrome.storage.local Usage

Local only - does NOT sync externally

Key Purpose
popupSize Remember popup window dimensions
scriptsyncinstances Allowed/blocked instance URLs for scriptsync
synctab Tab ID for scriptsync helper

Manifest Permissions Analysis

Chrome Manifest Permissions

8"permissions": [
9 "activeTab",
10 "declarativeContent",
11 "storage",
12 "contextMenus",
13 "cookies",
14 "sidePanel"
15],
127"host_permissions": [
128 "https://*.service-now.com/*"
129]

✓ Minimal permissions. Host access limited to ServiceNow domains only.

On-Prem Manifest Note

The on-prem version uses <all_urls> and *://*/* because on-premises ServiceNow instances use custom domains (not *.service-now.com). This is expected and documented in PRIVACY.md.

All Search Patterns & Results

fetch\s*\( 38 matches - all safe

All fetch calls use relative URLs to ServiceNow or chrome.runtime.getURL()

new XMLHttpRequest 3 matches - all safe

All XHR calls use relative URLs to ServiceNow APIs

new WebSocket 1 match - localhost only

Single WebSocket to ws://127.0.0.1:1978 for VS Code integration

sendBeacon|EventSource|RTCPeerConnection 0 matches

No tracking beacons, SSE, or WebRTC found

eval\( 0 matches (excluding Monaco)

No eval() in extension code. Monaco editor has standard eval for code execution.

tracking|analytics|telemetry|beacon 0 suspicious matches

Found in ServiceNow table names (sys_report) only, not telemetry code

createElement('script') 6 matches - all local

All use chrome.runtime.getURL() or snusettings.extensionUrl for local files