Why Proctoring Runs in a Cross-Origin Iframe

ProctorLink runs inside a cross-origin iframe, the enclave, served from its own domain so that the camera permission, the session token, and the capture code live on an origin you do not control. That boundary is what lets the camera permission persist across every site that embeds ProctorLink, keeps the session token out of host-page JavaScript, and keeps the integrity score computed server-side where a candidate cannot reach it. Your page adds one line of Content-Security-Policy and hands the SDK a token. Everything below is grounded in the ProctorLink API reference and architecture.

Schedule a Demo

What is the enclave, and why an iframe?

The ProctorLink SDK is a thin loader, about 3 KB gzipped. It does not capture anything itself. When you call it, it mounts a cross-origin iframe, the enclave, served from enclave.proctorlink.com. The camera, the session token, and the keyframe capture all live inside that frame, on an origin that belongs to ProctorLink rather than to you. Your page talks to it only through the SDK.

// In your exam page. The loader is about 3 KB gzipped.
// It mounts a cross-origin iframe (the enclave) served from
// enclave.proctorlink.com, not from your own origin.
import { ProctorLink } from '@proctorlink/sdk';

const session = ProctorLink.createSession({
  jwt: sessionJwt,            // short-lived, origin-bound session token
  sessionId,                  // session_id from your mint call
  mount: '#proctor-preview',  // where the enclave's preview sits (optional)
});

// Requests the camera against the ENCLAVE origin, not your domain.
await session.start();

Because the enclave is cross-origin, the browser treats it as a separate security context. Your page cannot read into it, and it cannot read out into your page, except through the narrow interface the SDK exposes. That single design choice is what makes the three properties below possible. If you are new to the integration as a whole, start with how to add proctoring to a web application, then come back here for why the boundary is drawn where it is. For the wider decision between an SDK, a REST API, and an LMS plugin, see proctoring SDK vs API vs LMS plugin.

Why does the camera permission persist across sites?

Browsers scope a camera grant to an origin. The enclave always loads from the same origin, enclave.proctorlink.com, no matter whose site embeds it. So a candidate who has already granted the camera to the enclave on one exam is not prompted again when they sit another exam on a different site that also embeds ProctorLink, because the browser sees the same iframe origin it granted before.

Run the same capture code as same-origin script in each customer page and you lose this. The browser would bind the grant to every customer domain separately and re-prompt on each one, which is friction at exactly the moment a candidate is nervous about starting. Putting the camera behind one stable origin trades a one-line CSP change for a permission that behaves consistently everywhere.

Where does the session token live?

Minting is a server-to-server call. Your backend calls POST /v1/sessions with your access-token and secret-token, and those credentials never leave your server. The browser receives only the session_jwt, a short-lived token scoped to one attempt and bound to the origins you listed in allowed_origins.

// Your backend. The API key stays here and never reaches the browser.
POST /v1/sessions
access-token: <YOUR_ACCESS_TOKEN>
secret-token: <YOUR_SECRET_TOKEN>
content-type: application/json

{
  "external_user_id": "candidate-123",
  "attempt_id": "attempt-789",
  "allowed_origins": ["https://exams.yourcompany.com"]
}

// The browser receives only this: a token scoped to one attempt and
// bound to your origin. It lives inside the enclave, not your JavaScript.
{
  "session_id": "6a7b18df70e4f8ecf2597b6f",
  "session_jwt": "eyJhbGciOi...",
  "expires_at": 1786443201
}

That token then lives inside the enclave, not in your page’s JavaScript. A script running on your page, whether it is your own, a third-party tag, or something injected, cannot read it, because it sits behind the cross-origin boundary. The same goes for the camera stream: the enclave holds it, and your page never touches raw video. The split is deliberate, and it is the same split described in the web application integration guide: the API key stays server-side, the browser only ever holds a token it cannot misuse.

WhatWhere it staysWhy
API key (access-token, secret-token)Your backend onlyNever sent to the browser. Anyone holding it can mint sessions billed to you.
Session token (session_jwt)Inside the enclaveShort-lived and origin-bound. Scoped to one attempt; safe to hand to the browser.
Integrity scoreComputed server-sideThe client cannot influence the verdict. This is the core anti-tamper property.

What do you have to change in your page?

One thing. If your page sets a Content-Security-Policy, allow the enclave origin as a frame source so the iframe can mount:

frame-src https://enclave.proctorlink.com;

You do not self-host the capture code, so there is nothing to add to script-src or connect-src for ProctorLink. By default the SDK shows a small floating picture-in-picture preview; pass a mount element to createSession to place that preview inside your own layout, or set showPreview to false to hide it. Either way the preview is still the enclave’s frame, so you are choosing where it sits, not pulling camera handling into your own code. One design consequence to know up front: a page that disables the camera wholesale through a browser Permissions-Policy will also stop the enclave from using it, so make sure the camera is not blocked globally.

How the boundary keeps the score tamper-resistant

The integrity score is computed server-side only. Nothing in the browser, inside or outside the enclave, can change the verdict, which is the core anti-tamper property of the design. The cross-origin boundary reinforces it from the other direction: a candidate cannot reach the capture code or the token from your page to feed the server false data. Keyframes upload from the enclave straight to object storage through presigned URLs, so the image bytes never pass through the API or through any code the candidate controls, and only periodic stills are captured rather than continuous video, which keeps bandwidth predictable.

A candidate can still refuse the camera or close the tab. Those are recorded as signals in the report, not as a way to edit a score. For what the browser can and cannot observe in the first place, and why the extension and desktop tiers exist, read what browser-based proctoring can and cannot detect. For how the server turns those signals into a verdict a reviewer can trust, see what AI proctoring measures.

PropertyIn the cross-origin enclaveIf it ran in your page
Camera permissionBinds to enclave.proctorlink.com. Granted once, reused on every site that embeds ProctorLink.Binds to each customer domain, so the browser re-prompts on every new site.
Session tokenHeld inside the enclave frame. Unreachable from host-page JavaScript.Sits in your page, readable by any script on it, including third-party or injected ones.
Camera streamHeld by the enclave origin. Your page never touches raw video.Flows through your own code, which you then have to secure.
Capture and uploadRuns in the enclave. Keyframes go straight to object storage via presigned URLs.Ships in your bundle, so you own and must maintain the capture path.
What you changeOne line of CSP: frame-src https://enclave.proctorlink.com.Self-hosted script plus wider script-src and connect-src rules.

Common mistakes with the enclave boundary

  • Forgetting the CSP frame-src. If your policy does not allow enclave.proctorlink.com, the browser blocks the iframe and the enclave never mounts, so capture never starts. Fix: add frame-src https://enclave.proctorlink.com; to your Content-Security-Policy.
  • Trying to read the token or camera stream from your page. The cross-origin boundary exists precisely so you cannot, so any code that depends on reaching into the enclave will fail. Fix: interact with the session only through the SDK methods and events.
  • Minting in the browser. Putting access-token and secret-token in client code hands anyone the ability to mint sessions billed to you, and it throws away the token model entirely. Fix: mint on your backend and pass the browser only session_jwt and session_id.
  • Building a re-consent screen for every domain. The camera permission binds to the enclave origin, not yours, so it does not re-prompt per site. Fix: rely on the enclave grant instead of adding your own repeat-consent UI for returning candidates.
  • Sandboxing or stripping the enclave with an over-broad policy. An aggressive CSP, a tracker blocker, or an iframe sandbox that removes camera access will break the enclave along with everything else. Fix: allow the enclave origin specifically and confirm the camera is not disabled by a global Permissions-Policy.

Why this design holds up

Put together, the choices reinforce one another. The API key stays on your server. The browser receives a short-lived, origin-bound token that lives inside the enclave rather than in your application state. The camera permission binds to the enclave origin, so a candidate who granted it once is not prompted again elsewhere. The integrity score is computed server-side, so the verdict cannot be edited from the exam page. Each property follows from the same cross-origin boundary rather than from a separate feature you have to configure.

If some of your exams run inside Moodle rather than your own engine, you do not wire the SDK there at all; you use the drop-in plugin covered in best Moodle proctoring plugin. Across published deployments, ProctorLink has supported more than one million proctored exam sessions (methodology note below). Full cohort sizes and outcomes are on the case studies page.

What customers say on G2

Institutions evaluating proctoring tools often look for independent feedback outside vendor case studies. ProctorLink is listed on G2, where Moodle administrators and training teams share verified product reviews.

Read ProctorLink reviews on G2 →

Frequently Asked Questions

Because the camera permission, the session token, and the capture logic need to live on an origin you do not control. The ProctorLink loader is a thin script (about 3 KB gzipped) that mounts a cross-origin iframe, the enclave, served from enclave.proctorlink.com. Running there rather than in your bundle is what lets the camera permission bind to a single origin, keeps the session token out of host-page JavaScript, and keeps the capture code identical across every site that embeds ProctorLink. Your page only has to allow the enclave origin in its Content-Security-Policy and hand the SDK a session token.

Sources & references

Deployment statistics and product behaviour described in this guide link to the sources below.

Next steps

Want to see the enclave mount and report a real attempt? Sign up at app.proctorlink.com to get your API keys, add the one-line CSP, and run a session end to end.

More Proctoring Guides

Run Proctoring Behind a Cross-Origin Enclave

Your backend mints a session, the loader mounts the enclave, and the camera, token, and capture stay on an origin the candidate cannot reach. Add one line of CSP and run a real attempt before you commit.