Skip to content

Harden GraphSandbox iframe before relaxing JSXGraph DSL validation #292

@brianlan

Description

@brianlan

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:

  1. Add a CSP <meta http-equiv="Content-Security-Policy"> to the blob iframe HTML.
  2. Deny connections, forms, objects, nested frames, workers, media, and base URL changes by default.
  3. Permit scripts only from the exact pinned jsDelivr JSXGraph URL/source and permit the minimum inline/eval capabilities required by the existing iframe renderer.
  4. Retain sandbox="allow-scripts" without allow-same-origin.
  5. 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:

  1. Select and pin an explicit JSXGraph release that renders the existing regression fixtures correctly.
  2. Add a restrictive CSP meta tag to generateIframeHtml() in frontend/src/components/GraphSandbox.tsx.
  3. Keep the iframe sandbox flags at allow-scripts only.
  4. Add unit tests asserting the expected CSP directives, pinned script URL, and sandbox attribute.
  5. Add a browser/E2E test that sends DSL attempting parent access and outbound requests, and verifies those actions do not succeed.
  6. Verify ordinary JSXGraph rendering, error propagation, reset, and graph switching still work.
  7. 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:

  1. Run the focused GraphSandbox unit tests.
  2. Run the relevant frontend browser/smoke tests.
  3. Open an existing graph problem and confirm behavior is unchanged while the current validator remains in place.
  4. Inspect the browser network panel while exercising malicious test DSL and confirm no forbidden request is sent.
  5. 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:

  • The iframe remains opaque-origin sandboxed with scripts only.
  • CSP directives deny outbound connections and unnecessary resource/navigation capabilities.
  • JSXGraph uses an explicit pinned version.
  • Browser-level tests prove parent access and outbound requests are blocked.
  • Existing GraphSandbox lifecycle and rendering tests still pass.
  • No frontend validator relaxation is bundled into this PR.
  • The PR description records commands, results, and manual verification.
  • Any CSP capability allowed by the implementation is justified and minimal.

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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions