# Load the detector script

> Add the detector via a Canvas theme JavaScript loader so it runs on quiz pages, and handle the self-hosted Canvas local-file (HTTP 422) case.

[Canonical HTML page](https://safeonlineexam.com/docs/canvas/detector-script)

The detector runs on Canvas quiz-taking pages. A Canvas access-code field is only
a challenge signal: before showing a protected SEB flow, the detector verifies the
exact course and assessment against the service. It fills the access code only
after Config Key proof, shows approved web tools, and detects Canvas-confirmed
completion. It reaches those pages by being loaded through your Canvas **account
theme** JavaScript.

The stable script URL is:

```text
${TOOL_URL}/js/canvas-seb-detector.js
```

When an institution hides the course-navigation item from students, the detector
does not rely on a rendered navigation link. On a protected assessment page it
uses the student's same-origin Canvas session to page through the course's
External Tools list and select the installed LTI 1.3 tool matching the configured
client and deployment IDs. On a sharded Canvas instance, that list can represent
the configured globally qualified client ID as its corresponding shard-local
Developer Key ID; the detector recognizes that form while rejecting different,
malformed, or zero IDs. If that Canvas lookup is unavailable, it retains a visible
matching navigation link as a compatibility fallback. If neither is available, the
launch prompt asks the student to reload and contact their instructor or Canvas
administrator instead of sending them to the course home.

Keep the external app installed in the course or inherited account even when its
student navigation tab is hidden. This discovery is bounded and does not change
the application's Canvas OAuth scope contract.

<Callout title="Cloud Run release bundles can render the loader">
  After the Cloud Run bundle's `prepare.sh` stage, run
  `./canvas-theme-loader.sh cloudrun.env`. It writes an upload-ready
  `canvas-theme-loader.js` for the configured public origin, with no secret
  material. Upload that generated file as the theme's desktop JavaScript rather
  than transcribing the detector URL by hand.
</Callout>

## Create the loader [#create-the-loader]

Upload a small loader (not the detector itself) as the theme's desktop
JavaScript. Replace `${TOOL_URL}` before uploading.

```javascript
(function () {
  "use strict";

  const detectorUrl = "${TOOL_URL}/js/canvas-seb-detector.js";
  const assessmentPath = /^\/courses\/\d+\/(?:quizzes\/\d+\/take|assignments\/\d+)(?:\/|$)/;

  if (!assessmentPath.test(window.location.pathname)) {
    return;
  }
  if (document.querySelector('script[data-canvas-seb-detector="true"]')) {
    return;
  }

  const script = document.createElement("script");
  script.src = detectorUrl;
  script.async = true;
  script.dataset.canvasSebDetector = "true";
  document.head.appendChild(script);
})();
```

The pattern matches Classic Quiz `/take` pages and New Quiz assignment routes
(including their Canvas-generated descendants). `script.src` must be a plain
JavaScript URL string — not a Markdown link.

<Steps>
  <Step>
    ### Upload as theme JavaScript [#upload-as-theme-javascript]

    Upload the loader as the account theme's **desktop JavaScript** and apply the theme
    at the intended account scope.
  </Step>

  <Step>
    ### Confirm it applies at the right scope [#confirm-it-applies-at-the-right-scope]

    Theme upload capability and inherited-theme behavior vary by Canvas configuration.
    If the upload control is unavailable, resolve that account setting before treating
    the detector as installed.
  </Step>

  <Step>
    ### Retest after Canvas changes [#retest-after-canvas-changes]

    Retest the loader after significant Canvas theme, CSP, or quiz-rendering changes.
  </Step>
</Steps>

## Self-hosted Canvas: the local-file (HTTP 422) case [#self-hosted-canvas-the-local-file-http-422-case]

Some self-hosted Canvas deployments store an uploaded theme JavaScript file as a
local `/accounts/:accountId/files/:fileId/download` attachment. Rails can reject
that response with `ActionController::InvalidCrossOriginRequest` (HTTP `422`), so
the loader never reaches the service. This is a Canvas attachment-serving
limitation — not an LTI, detector, or deployment-ID failure.

<Callout type="warn" title="Do not disable CSRF protection">
  When the browser console shows that `422`, do **not** disable Canvas CSRF
  protection globally. Instead, point the account theme's `js_overrides` value at the
  externally hosted loader:

  ```text
  ${TOOL_URL}/js/canvas-seb-theme-loader.js
  ```
</Callout>

The hosted loader keeps the same quiz-route scope and loads the full detector only
on Classic Quiz `/take` pages and New Quiz assignment routes. Canvas's Theme Editor
has no direct URL field, so set this through your provisioning/administration path
and preserve it on future theme saves. For a one-off root-account recovery, run
this inside the Canvas web container (replace the account ID and URL):

```bash
bundle exec rails runner '
account = Account.find(1)
current = account.brand_config || BrandConfig.default
attrs = current.attributes.slice(*BrandConfig::ATTRS_TO_INCLUDE_IN_MD5.map(&:to_s))
attrs["js_overrides"] = "https://seb.example.edu/js/canvas-seb-theme-loader.js"
replacement = BrandConfig.for(attrs.symbolize_keys)
replacement.save_unless_dup!
replacement.sync_to_s3_and_save_to_account!(nil, account)
'
```

For a broader deployment, configure S3-compatible attachment storage and re-upload
the theme file — Canvas then serves uploaded theme assets from object storage
instead of the local files controller. That is the durable fix for arbitrary
uploaded theme JavaScript; the hosted loader is the smallest safe fix for this
integration.

<Callout>
  The service also exposes `/api/seb/canvas-detector.js` as a compatibility alias for
  an existing installation that uses that path. New loaders should use
  `/js/canvas-seb-detector.js`.
</Callout>

## Verify the detector loaded [#verify-the-detector-loaded]

In a normal browser, open a Classic Quiz `/take` page and a New Quiz assignment
route and confirm the detector script loads without console errors, and that
`${TOOL_URL}/js/canvas-seb-detector.js` returns JavaScript. If it never loads, see
[Troubleshooting → Detector](/docs/troubleshooting/detector).

Next: verify everything end to end in [Roll out & operate](/docs/operate).