Skip to main content
Safe Online Exam injects a JavaScript detector into Canvas quiz pages that reads the SEB Config Key hash, verifies it against the server, and fills the Canvas access-code prompt only when SEB is running the current configuration. This page documents the routes that deliver the detector and its associated assets — the stable script URL, the hosted Canvas theme loader, the compatibility alias, the debug trace endpoint, and the server-owned YouTube player — together with the behaviour and security constraints of each. The approved-tools endpoint (GET /api/seb/tools/:courseId/:quizId) is documented on the SEB Student Routes page.

Detector Script Delivery

GET /js/canvas-seb-detector.js

The stable, canonical URL for the SEB detector script. Canvas theme loaders and js_overrides configurations should always point to this path. The server injects deployment-specific values into the script at response time: the application base URL, the LTI client ID, the LTI deployment ID list, and the debug/diagnostics flags. In production mode with a configured base URL the injected response is cached in-process for identical configuration tuples, and the same cached bytes are served to every subsequent request with the same parameters. Debug and diagnostics modes serve a non-cached, human-readable version of the script so issues can be diagnosed without a deployment rebuild. Authentication: None. This is a public, unauthenticated endpoint. Cache behaviour The cache-control response header depends on the deployment mode: The response also sets pragma: no-cache, expires: 0, and vary: Origin, X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-Port so reverse proxies and CDNs do not cache the injected deployment constants across different origins. Response
The response body is the assembled, deployment-configured JavaScript detector.

GET /api/seb/canvas-detector.js

A stable compatibility alias for /js/canvas-seb-detector.js. Canvas theme loaders that were configured before the primary URL was established continue to work without reconfiguration. This route is handled by the same controller method and produces identical output to /js/canvas-seb-detector.js — same injection, same cache headers, same vary directive. No request is ever redirected; the alias is a true second route on the same handler.
New Canvas theme loader configurations should use /js/canvas-seb-detector.js. The compatibility alias at /api/seb/canvas-detector.js is maintained for existing deployments.

GET /js/canvas-seb-theme-loader.js

A minimal Canvas theme loader hosted by the application rather than uploaded to Canvas Files. Some self-hosted Canvas deployments reject locally stored theme JavaScript with Rails’ InvalidCrossOriginRequest protection. Pointing Canvas’s js_overrides theme setting at this endpoint avoids that rejection while retaining the quiz-page-only detector loading behaviour. Authentication: None. Public endpoint. What the loader does The loader is a self-invoking function that runs on every Canvas page load:
  1. Matches the current path against Classic Quiz take routes (/courses/:courseId/quizzes/:quizId/take) and New Quiz assignment routes (/courses/:courseId/assignments/:assignmentId). It exits immediately on non-quiz pages and on New Quiz authoring paths (build, settings, moderate, reports, exports).
  2. Constructs the canonical content ID — classicquiz_{quizId} for Classic Quizzes or newquiz:{courseId}:{assignmentId} for New Quizzes.
  3. Calls GET /api/seb/requirement/:courseId/:contentId with credentials: omit and cache: no-store. If the response is not { success: true, sebRequired: true }, it exits without loading the detector.
  4. If the requirement check confirms SEB is required and no detector script tag is already present, it dynamically appends a <script> element pointing to /js/canvas-seb-detector.js.
This two-stage approach means the full detector script is downloaded only on assessed quiz pages where SEB enforcement is active. Cache behaviour Same as the detector script: no-cache, must-revalidate in normal mode; no-cache, no-store, must-revalidate when debug is enabled.

What the Detector Does on Canvas Quiz Pages

Once loaded, the detector:
  1. Reads SafeExamBrowser.security.configKeyHash and the current window.location.href from the SEB JavaScript API.
  2. Calls POST /api/seb/access-proof/:courseId/:quizId with the Config Key hash and URL as the request body.
  3. If the proof request succeeds, immediately calls POST /api/seb/access-code/:courseId/:quizId with the proofToken in the x-seb-proof-token request header.
  4. Fills the Canvas access-code input when — and only when — Canvas presents an unambiguous access-code prompt, and the server returned a valid access code. The detector does not treat DOM content as authorisation; it fills a field only when the server has already validated SEB’s identity.
  5. Displays approved exam tools from the tools array in the access-code response.
  6. Waits for Canvas-authored completion evidence (Classic Quiz: successful final submission with a matching Canvas result structure; New Quiz: the authoritative result UI) before using the exit grant to display the settings-bound quit link.
In an ordinary browser (no SEB JavaScript API), SafeExamBrowser.security.configKeyHash is absent. The detector calls GET /api/seb/requirement/:courseId/:quizId first and shows the SEB-required prompt only when the server confirms SEB is needed — it never shows the prompt based on DOM inspection alone.
The detector sidebar and its buttons are affordances only. The SEB URL filter in the generated .seb configuration is the control that determines what can load inside SEB. Changing any selected exam tool or URL policy rule changes the configuration fingerprint; students must download a fresh .seb file before those changes take effect.

Why the Detector Is One Script

The public detector is served as a single JavaScript response. Its maintainable source is broken into modules assembled in manifest.json order during development, tests, and the build. At runtime:
  • The Canvas theme loader already limits detector loading to supported assessment routes where the requirement check returns sebRequired: true. Splitting the detector into multiple browser requests would lose cross-fragment HTTP compression and introduce startup-ordering risk if a later fragment depends on state set by an earlier one.
  • The in-process cache for production responses means all concurrent students on the same page receive the same compressed bytes without additional database or filesystem reads.
  • Diagnostics mode serves the readable, unminified assembly so issues can be traced in the browser console without a source map.

Requirement Check Caching

GET /api/seb/requirement/:courseId/:quizId performs one exact primary-key lookup against the assessments table and coalesces concurrent requests for the same assessment through a short, bounded in-process promise cache. The cache is stored per (courseId, canonicalContentId) tuple. All concurrent checks for the same assessment join the same pending promise instead of issuing parallel database reads. The response always carries cache-control: private, no-store, max-age=0. Browsers and proxies must not cache the requirement result; staleness could cause the detector to show or hide the SEB-required prompt incorrectly after an instructor changes the assessment policy.

Debug Trace Endpoint

POST /api/debug/canvas-detector-trace

Accepts sanitised diagnostic events from the detector when the deployment has debug or diagnostics mode enabled.
This endpoint is disabled in all normal production deployments. It returns { "enabled": false } immediately — without logging anything — unless both SEB_DEBUG_ENABLED or SEB_DETECTOR_DIAGNOSTICS_ENABLED is set and the TESTBED_ENABLED flag is active. Never enable the testbed flag in production; it is intended only for controlled non-production test environments.
Authentication: None. However, the server enforces three independent guards:
  1. Both debugEnabled (or detectorDiagnosticsEnabled) and testbed.enabled must be true in the application configuration.
  2. The Origin header must exactly match the configured Canvas domain origin. Requests from any other origin return { "enabled": false } without recording.
  3. A per-origin rolling window rate limit of 120 requests per 60 seconds is enforced. Exceeding it returns 429.
Request body The body is any JSON-serialisable value. The DetectorTraceService sanitises and records the payload. When detectorDiagnosticsEnabled is true, additional detail fields are recorded; when only debugEnabled is true, the recorded payload is reduced. Success response — 200
Disabled or rejected response — 200
The endpoint always returns HTTP 200 when the feature is disabled to avoid leaking configuration information to unauthenticated callers. A 429 is the only non-200 response, and only when the rate limit is exceeded on an otherwise-enabled endpoint.

Approved Tools Under Proof/Session Boundary

The detector calls GET /api/seb/tools/:courseId/:quizId to refresh the approved exam tool list after an access-code redemption, without re-running the full proof cycle. This endpoint is documented in full on the SEB Student Routes page, which covers all student-facing SEB endpoints including the tools route.

Server-Owned YouTube Player

GET /seb/tool/youtube/:videoId

Renders a server-owned single-video YouTube player page. Instructors can create a dedicated YouTube video tool that accepts a watch, share, Shorts, or embed link; the server converts the accepted formats into this endpoint with the canonical YouTube video ID. YouTube requires a valid embedding identity (a stable HTTPS origin in the embed URL’s origin parameter). Because SEB opens tools in a separate window, the player page is the embedding document, and the server supplies that stable origin rather than relying on the SEB window’s origin. Authentication: None. Public endpoint. Path parameters
string
required
An 11-character YouTube video ID. Must match [A-Za-z0-9_-]{11}. Any other value returns 404.
Accepted source URL forms (resolved at tool-creation time, not at this endpoint)
What the player deliberately excludes The server-owned player page uses a strict Content Security Policy that allows only the YouTube embed frame and no other content. Specifically:
  • YouTube browsing (www.youtube.com navigation, channel pages, search) is not available from this page.
  • Google sign-in flows are blocked; the page does not include accounts.google.com in any allow-list.
  • No scripts, images, or styles other than inline <style> are permitted.
  • No <form> submissions and no <base> element are allowed.
This bounded media policy means the approved tool gives students access to exactly one video, not to YouTube as a platform. The SEB URL filter in the generated configuration controls what domains can load at the network level; the player page restricts what the embedding document itself does. Response — 200
The response body is a minimal HTML page containing a single full-viewport <iframe> pointing to https://www.youtube.com/embed/<videoId>?rel=0&origin=<applicationOrigin>. Error response