Skip to content

Fixes from the security review of the 1.5.0 changes - #221

Merged
antiv merged 1 commit into
mainfrom
security-fixes
Oct 11, 2026
Merged

antiv merged 1 commit into
mainfrom
security-fixes

Conversation

@antiv

@antiv antiv commented Oct 11, 2026

Copy link
Copy Markdown
Owner

An independent review covered everything merged since #194: CSP steps 3–6, costs and dollar budgets, the Playground, the MySQL fixes and the contributor PRs. It found nothing critical or high. This PR fixes the two medium findings and two of the low ones. The third low one, dollar budgets that don't count unpriced calls, is a design question and gets its own issue.

Python in the Work Room canvas ran unsandboxed (medium)

  • The problem:
    • CSP step 5 moved HTML, JS, SVG and CSS onto the sandboxed /dashboard/workroom/canvas page. Python stayed in a srcdoc iframe without sandbox, on MATE's origin. The code comment called it safe because "users run their own code", but an agent writes it.
    • Instructions planted in what the agent reads (a web page, a document) could produce Python whose js module acts as whoever clicks Run. For an admin, that means creating users or reading agent configs, keys included.
  • The fix:
    • The Work Room reads static/js/workroom-python.js and sends it inline, with Pyodide, to the sandboxed canvas page, exactly like JS. The page can't load files from MATE itself on a loopback or private address (Chrome).
    • Open in new tab now runs Python there too. It used to wrap Python in a <script>.
    • The old note that Pyodide can't work in a sandbox was wrong. It runs there.

CSP allowed whole CDN hosts for scripts (medium)

  • The problem: script-src allowed all of cdn.jsdelivr.net and cdnjs.cloudflare.com. Those serve any npm package or GitHub repository (/gh/<user>/<repo>), so any HTML injection could load an attacker's script even under CSP_MODE=enforce.
  • The fix:
    • _SCRIPT_HOSTS now lists each library's path with its version, as a prefix or an exact path: Chart.js, Monaco, React and React DOM, React Flow, markdown-it, Swagger UI, Redoc, Ace. Tailwind's host stays whole, since it serves only Tailwind.
    • 'wasm-unsafe-eval' is dropped from the dashboard's policy, because Pyodide no longer runs there.
    • Styles, fonts and connections keep the whole hosts; none of them can run code.
    • test_every_cdn_script_in_the_templates_is_allowed fails when a template loads a CDN script the policy doesn't cover.

OpenRouter prices weren't validated (low)

  • The problem: float("NaN") and float("inf") passed the < 0 check. A NaN cost makes SQL sums NaN, which blocks every request in a dollar-budget scope and breaks the alert's percentage.
  • The fix:
    • prices must be finite and between 0 and $0.1 per token, the same ceiling as manual prices;
    • the response is streamed and abandoned past 25 MB.

Playground runs weren't tied to a person (low)

  • The problem: runs are logged under the eval user, with no audit entry.
  • The fix: evals.playground_run is written before the run, so a failed or hung run is recorded too. It names the admin and records the mode, the number of cases, and each variant's model and version. Changed instructions are recorded by length and a SHA-256 prefix, not as text.

Checks

  • Browser, CSP_MODE=enforce, Chromium:
    • Python in the canvas runs (sum 5), and the iframe has sandbox="allow-scripts allow-modals" and loads the canvas page.
    • From Python, js.window.parent.document and js.localStorage both fail.
    • No CSP violations on the Work Room, Usage, Docs, Agents, the visual builder or /admin-docs. Each library is loaded: Chart, markdownit, ReactFlow and React, SwaggerUIBundle, ace, and the Monaco loader.
    • A script injected from https://cdn.jsdelivr.net/gh/jquery/jquery@3.7.1/... is blocked (script-src-elem).
  • Unit tests: 1456 OK. New tests:
    • scripts allowed by path and not by host;
    • every template CDN script covered;
    • NaN, infinite and too-high OpenRouter prices skipped;
    • the response size cap;
    • the Playground audit entry, with no instruction text, and none for a refused run.
  • gen_docs.py --check: OK.
  • gen_docs.py drift:
    • updated docs/dev/content-security-policy.md (script paths; Python in the sandbox; the Python canvas gap removed), docs/user/work-room.md, docs/user/settings.md and docs/user/evals.md;
    • eu-ai-act.md, getting-started.md, usage-and-audit.md, deployment.md and template-format.md are flagged only because files they cover changed, and they still hold.

Python in the Work Room canvas ran in an unsandboxed frame on MATE's
origin. Agent-written Python could use Pyodide's js module to act as the
person who clicked Run. It now runs on the sandboxed canvas page, like
HTML and JavaScript, and Open in new tab runs it there too.

script-src allowed all of cdn.jsdelivr.net and cdnjs.cloudflare.com,
which serve any npm package or GitHub repository, so an HTML injection
could load an attacker's script under enforce. It now lists each
library's path with its version, a test fails on a template script it
does not cover, and 'wasm-unsafe-eval' is gone with Pyodide.

OpenRouter prices must be finite and at most $0.1 per token, so a NaN
cannot poison cost sums and dollar budgets, and the list is read up to
25 MB. Each Playground run writes an audit entry naming the admin and
what was compared.
@antiv
antiv merged commit 12a2476 into main Oct 11, 2026
2 checks passed
@antiv
antiv deleted the security-fixes branch October 11, 2026 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant