Is Secure Exam Proctor safe?
Secure Exam Proctor conceals all server domain names and API paths inside integer byte arrays throughout its code.
The extension stores more than 30 strings — including its server hostnames, API route segments, and a hardcoded AES-CBC decryption key — as Uint8Array integer sequences that must be decoded at runtime rather than appearing as readable text. This pattern covers every network-facing constant: the proctorauth.com API base, the proctor.io WebSocket host, and the telemetry.proctorcollect.com endpoint. Standard review tools that scan for domain names in source code will not surface these destinations without first running the decode step.
Who publishes itProctorio Inc. - no other listings under this identity, 1 shared hostname
Proctorio Inc. - no other listings under this identity, 1 shared hostname
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 1 hostname
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Server domains and API paths stored inside byte arrays
The extension builds server domains, API paths, and crypto IDs at runtime from byte arrays, not text.
A hardcoded AES-CBC fallback key decodes from a Uint8Array via TextDecoder.
A PoC decoded 108 such patterns, unsearchable as plaintext.
You install the extension and it begins a proctored session.
The extension assembles server addresses and API paths from integer byte arrays; they are not written anywhere as readable text in the source code.
A static analysis tool or curious developer searching the source for 'proctorauth.com/scm/session/create' will find nothing; the string is constructed at call time.
This is how the 'proctorauth.com/scm/' base path appears in the shipped source. Without running the code, the destination server is not visible to a human reader.
proctorauth.com/scm/
How session/create and session/end endpoints are constructed at runtime
// Wo() returns 'proctorauth.com/scm/'
// 'session/create' is decoded inline from [115,101,115,115,105,111,110,47,99,114,101,97,116,101]
const endpoint = `https://${subdomain}${isStaging ? '-staging' : ''}.proctorauth.com/scm/session/create`;
const response = await fetch(endpoint, { method: 'POST', body: formData, headers: authHeaders }, 60000, 1);// 'session/end' is decoded inline from [115,101,115,115,105,111,110,47,101,110,100]
const endpoint = `https://${subdomain}${isStaging ? '-staging' : ''}.proctorauth.com/scm/session/end`;
await fetch(endpoint, { method: 'POST', body: formData, headers: authHeaders }, 60000, 1);// Primary path: decrypt WebSocket payload with session key from server
try {
return JSON.parse(atob(await decryptAES(parts[1], sessionKey, parts[0])));
} catch (e) {
// Fallback: decrypt with hardcoded 32-byte key 'AF1sUGvsvUsbjx5nr04OTzZ9CFYULUXw'
const fallbackKey = new Uint8Array([65,70,49,115,85,71,118,115,118,85,115,98,106,120,53,110,114,48,52,79,84,122,90,57,67,70,89,85,76,85,88,119]);
return JSON.parse(atob(await decryptAES(parts[1], fallbackKey, parts[0])));
}- proctorauth.com
Primary backend. Session lifecycle endpoints (session/create, session/end) POST to <subdomain>.proctorauth.com/scm/.
- cdn.proctorauth.com
Content delivery origin for the extension's proctor assets.
- proctor.io
Primary product domain. WebSocket endpoints (gbl4845ws.proctor.io, gbl0317ws.proctor.io) constructed from encoded strings for real-time proctoring traffic.
- us1.gabbath.com
Session URL encoded in Js2Q.js: 'https://us1.gabbath.com/session?deviceid=&userid='. Purpose unclear from static analysis alone.
- telemetry.proctorcollect.com
Telemetry endpoint. Domain appears both as plaintext and was identified in related static analysis.
Scans the extension's main service worker (Js2Q.js) for all Uint8Array byte sequences and decodes them to plaintext, revealing domain names, API paths, cryptographic identifiers, and the hardcoded AES-CBC fallback key. Run it against a local copy of the extension.
/**
* Decode Uint8Array-encoded strings from Secure Exam Proctor (fpmapakogndmenjcfoajifaaonnkpkei)
* Claim 6701 reproducer — CWE-506 (Embedded Malicious Code / string obfuscation)
*
* Usage:
* node secure-exam-proctor-decode-strings.js /path/to/extracted/assets/Js2Q.js
*/
const fs = require('fs');
const path = require('path');
const jsPath = process.argv[2] || path.join(__dirname, 'assets/Js2Q.js');
if (!fs.existsSync(jsPath)) {
console.error('File not found:', jsPath);
process.exit(1);
}
const js = fs.readFileSync(jsPath, 'utf8');
const decoder = new TextDecoder();
const regex = /new Uint8Array\(\[([^\]]+)\]\)/g;
let m;
const results = [];
while ((m = regex.exec(js)) !== null) {
const bytes = m[1].split(',').map(Number);
try {
const decoded = decoder.decode(new Uint8Array(bytes));
results.push({ pos: m.index, length: bytes.length, text: decoded });
} catch (e) { /* ignore non-UTF-8 */ }
}
console.log(`Total new Uint8Array([...]) patterns in file: ${results.length}`);
console.log('\nAll decoded strings:\n');
results.forEach(({ pos, length, text }) => {
console.log(` offset ${pos.toString().padStart(7)} [${length} bytes]: ${JSON.stringify(text)}`);
});
// Highlight the hardcoded AES-CBC fallback key
const KEY_BYTES = [65,70,49,115,85,71,118,115,118,85,115,98,106,120,53,110,114,48,52,79,84,122,90,57,67,70,89,85,76,85,88,119];
const key = decoder.decode(new Uint8Array(KEY_BYTES));
console.log('\n=== Hardcoded AES-CBC fallback decryption key ===');
console.log(` Value: ${JSON.stringify(key)}`);
console.log(` Length: ${key.length} bytes`);
console.log(' Location: catch-block at ~offset 375114 in Js2Q.js');
console.log(' Used when: server-supplied session key fails to decrypt WebSocket payload');
- 1Download the extension and unzip the .crx.
- 2Run: node secure-exam-proctor-decode-strings.js /path/to/extracted/assets/Js2Q.js.
- 3Review the output: Uint8Array patterns decode, including the hardcoded AES-CBC fallback key.