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.
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:
${TOOL_URL}/js/canvas-seb-detector.jsWhen 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.
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.
Create the loader
Upload a small loader (not the detector itself) as the theme's desktop
JavaScript. Replace ${TOOL_URL} before uploading.
(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.
Upload as theme JavaScript
Upload the loader as the account theme's desktop JavaScript and apply the theme at the intended account 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.
Retest after Canvas changes
Retest the loader after significant Canvas theme, CSP, or quiz-rendering changes.
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.
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:
${TOOL_URL}/js/canvas-seb-theme-loader.jsThe 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):
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.
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.
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.
Next: verify everything end to end in Roll out & operate.
Install the external app
Install the registered app by client ID at the right account scope, record the deployment ID, and choose between strict and self-service deployment-ID policy.
Self-hosted Canvas
Endpoint overrides, issuer rules, platform signing-key requirements, and the launch-error recovery table for Canvas instances you run yourself.