Summary
Harden the GraphSandbox iframe so stored or model-generated JSXGraph JavaScript executes with explicit browser-level confinement instead of relying primarily on a hand-written JavaScript allowlist.
Background / Context
LearnLoop renders graphDsl and coaching whiteboard_dsl by creating a JSXGraph board inside a blob-backed iframe and executing the supplied string with new Function("board", dsl).
The iframe currently has sandbox="allow-scripts" without allow-same-origin, which isolates it from the parent origin and DOM. However, the generated iframe document has no Content Security Policy, loads an unpinned JSXGraph script from jsDelivr, and can still initiate network requests from executed DSL.
A strict frontend DSL allowlist was added in commit bc5b113 as a security control. That parser is not compatible with valid historical graph DSL and is now blocking all active graph problems. A separate issue will remove the incompatible semantic restrictions, but that must not merge until the iframe has stronger browser-enforced confinement.
Problem
The current security model mixes two concerns:
- Browser isolation is incomplete because the iframe lacks a restrictive CSP.
- The frontend validator attempts to parse and authorize a small custom subset of JavaScript, which is brittle and incompatible with previously stored graphs.
Removing or relaxing the validator before strengthening the iframe would allow stored or model-generated DSL to make network requests or exercise arbitrary browser APIs inside the sandbox.
Goal / Expected Behavior
GraphSandbox should execute graph DSL in an iframe that:
- Cannot access the parent DOM or application origin.
- Cannot make
fetch, XHR, WebSocket, EventSource, beacon, form, or other outbound application-data requests.
- Cannot create nested browsing contexts, load objects, or navigate the parent page.
- Can load and execute only the required pinned JSXGraph runtime plus the sandbox's own rendering code.
- Preserves current rendering, error reporting, reset, timeout, and postMessage behavior.
Scope
This issue should cover:
- Add an explicit restrictive CSP to the generated iframe document.
- Keep
sandbox="allow-scripts" and do not add allow-same-origin, forms, popups, or top-navigation permissions.
- Pin the JSXGraph CDN URL to one explicit, verified version instead of using the unspecified latest version.
- Add automated coverage for the generated security policy and sandbox flags.
- Add browser-level verification that attempted outbound requests and parent access from DSL are blocked.
Out of Scope
This issue should not cover:
- Removing or relaxing the frontend semantic DSL validator.
- Rewriting historical graph DSL.
- Changing the backend coaching DSL sanitizer.
- Refactoring unrelated iframe lifecycle or JSXGraph rendering code.
- Adding new JSXGraph element types or graph features.
Chosen Implementation Approach
Use browser-enforced isolation as the primary execution boundary:
- Add a CSP
<meta http-equiv="Content-Security-Policy"> to the blob iframe HTML.
- Deny connections, forms, objects, nested frames, workers, media, and base URL changes by default.
- Permit scripts only from the exact pinned jsDelivr JSXGraph URL/source and permit the minimum inline/eval capabilities required by the existing iframe renderer.
- Retain
sandbox="allow-scripts" without allow-same-origin.
- Keep the existing source-window check for postMessage handling.
The CSP must be tested in a real browser. A string assertion alone is not sufficient to establish that network requests are blocked.
Implementation Plan
The implementor should:
- Select and pin an explicit JSXGraph release that renders the existing regression fixtures correctly.
- Add a restrictive CSP meta tag to
generateIframeHtml() in frontend/src/components/GraphSandbox.tsx.
- Keep the iframe sandbox flags at
allow-scripts only.
- Add unit tests asserting the expected CSP directives, pinned script URL, and sandbox attribute.
- Add a browser/E2E test that sends DSL attempting parent access and outbound requests, and verifies those actions do not succeed.
- Verify ordinary JSXGraph rendering, error propagation, reset, and graph switching still work.
- Document the chosen JSXGraph version and why the minimum required CSP script permissions are necessary in code comments or the PR description.
Relevant Files / Areas
Likely relevant areas:
frontend/src/components/GraphSandbox.tsx
frontend/src/components/GraphSandbox.test.tsx
frontend/tests/
frontend/src/visual-test-harness.html
frontend/package.json
npm test -- --run src/components/GraphSandbox.test.tsx
npm run test:smoke:compose
Tests Required
The implementor must add or update automated tests covering:
- The iframe retains
sandbox="allow-scripts" and does not gain allow-same-origin.
- The generated iframe contains the intended restrictive CSP.
- JSXGraph is loaded from an explicitly pinned version.
- Normal graph DSL still renders successfully.
- DSL cannot successfully read or modify the parent document.
- DSL attempts using
fetch or equivalent outbound connection APIs do not reach a test endpoint.
- Existing error, timeout, reset, and prop-change behavior remains intact.
At minimum, tests should verify:
- The main graph-rendering happy path.
- Browser-enforced network denial.
- Parent-origin isolation.
- Existing postMessage lifecycle behavior.
Manual Verification / Self-Check
Before claiming this issue is done, the implementor must:
- Run the focused GraphSandbox unit tests.
- Run the relevant frontend browser/smoke tests.
- Open an existing graph problem and confirm behavior is unchanged while the current validator remains in place.
- Inspect the browser network panel while exercising malicious test DSL and confirm no forbidden request is sent.
- Record the exact commands run and their results in the PR description.
Suggested verification commands:
cd frontend
npm test -- --run src/components/GraphSandbox.test.tsx
npm run build
npm run test:smoke:compose
Reviewer Acceptance Checklist
The reviewer should verify that:
Dependencies
None.
Follow-Up Work
After this issue is complete:
Definition of Done
This issue is done when:
- The iframe has a tested restrictive CSP and retains opaque-origin sandboxing.
- JSXGraph is loaded from a pinned verified version.
- Automated browser tests demonstrate blocked parent access and blocked outbound requests.
- Existing rendering and lifecycle tests pass.
- The PR includes exact automated and manual verification results.
Summary
Harden the
GraphSandboxiframe so stored or model-generated JSXGraph JavaScript executes with explicit browser-level confinement instead of relying primarily on a hand-written JavaScript allowlist.Background / Context
LearnLoop renders
graphDsland coachingwhiteboard_dslby creating a JSXGraph board inside a blob-backed iframe and executing the supplied string withnew Function("board", dsl).The iframe currently has
sandbox="allow-scripts"withoutallow-same-origin, which isolates it from the parent origin and DOM. However, the generated iframe document has no Content Security Policy, loads an unpinned JSXGraph script from jsDelivr, and can still initiate network requests from executed DSL.A strict frontend DSL allowlist was added in commit
bc5b113as a security control. That parser is not compatible with valid historical graph DSL and is now blocking all active graph problems. A separate issue will remove the incompatible semantic restrictions, but that must not merge until the iframe has stronger browser-enforced confinement.Problem
The current security model mixes two concerns:
Removing or relaxing the validator before strengthening the iframe would allow stored or model-generated DSL to make network requests or exercise arbitrary browser APIs inside the sandbox.
Goal / Expected Behavior
GraphSandboxshould execute graph DSL in an iframe that:fetch, XHR, WebSocket, EventSource, beacon, form, or other outbound application-data requests.Scope
This issue should cover:
sandbox="allow-scripts"and do not addallow-same-origin, forms, popups, or top-navigation permissions.Out of Scope
This issue should not cover:
Chosen Implementation Approach
Use browser-enforced isolation as the primary execution boundary:
<meta http-equiv="Content-Security-Policy">to the blob iframe HTML.sandbox="allow-scripts"withoutallow-same-origin.The CSP must be tested in a real browser. A string assertion alone is not sufficient to establish that network requests are blocked.
Implementation Plan
The implementor should:
generateIframeHtml()infrontend/src/components/GraphSandbox.tsx.allow-scriptsonly.Relevant Files / Areas
Likely relevant areas:
frontend/src/components/GraphSandbox.tsxfrontend/src/components/GraphSandbox.test.tsxfrontend/tests/frontend/src/visual-test-harness.htmlfrontend/package.jsonnpm test -- --run src/components/GraphSandbox.test.tsxnpm run test:smoke:composeTests Required
The implementor must add or update automated tests covering:
sandbox="allow-scripts"and does not gainallow-same-origin.fetchor equivalent outbound connection APIs do not reach a test endpoint.At minimum, tests should verify:
Manual Verification / Self-Check
Before claiming this issue is done, the implementor must:
Suggested verification commands:
Reviewer Acceptance Checklist
The reviewer should verify that:
Dependencies
None.
Follow-Up Work
After this issue is complete:
Definition of Done
This issue is done when: