Repository navigation
test(security): the web UI and what its origin serves - #406
Merged
Merged
Conversation
…e UI origin Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NSpbAKgGb282ixhsEqGQ52
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NSpbAKgGb282ixhsEqGQ52
Planting it with localStorage.setItem from the spec tripped CodeQL's clear-text-storage rule on a fake value; going through the token popover is also the path a real user's token takes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NSpbAKgGb282ixhsEqGQ52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second pass after dkackman/diffusers-workflow#391, which covered the engine, API routes and MCP tools but not the browser. Tests only; no production or UI source changes. Two files:
ui/e2e/security.spec.ts(Playwright, real server + real Chromium): 10 tests, one of them atest.failfinding.tests/test_security_web_headers.py(pytest): 21 passing, 11 strict xfails.Why this matters: the UI stores the API token in
localStorage, and the same origin serves the UI,/outputsand/inputs, neither of which needs a token. So any script that runs on that origin owns the token.Method (e2e). In a throwaway workspace (
e2e-xss, deleted inafterAll) the spec plants payloads in every field an untrusted author controls:text/htmlIt then visits each page that shows them. Every payload sets
document.documentElement.dataset.xss, and alerts are caught as dialogs. Each check first waits for the payload to be visible as text, so a page that failed to render can't pass. It also asserts that noimg[src=x],[onerror]/[onload]element orjavascript:link ended up in the DOM.Coverage
security.spec.ts: workflow catalog, workflow page, workflow editor (Monaco), gallery + opening a hostile-named tile, assets, prompt library, prompt editor, job page{@html}orinnerHTMLinui/srcsecurity.spec.ts › a run that writes text/htmltest_security_web_headers.py::TestActiveOutputs::test_outputs_does_not_serve_it_as_a_live_document[.html,.xhtml,.xml,.svg],test_keep_output_cannot_carry_one_into_assetstest_the_engine_writes_it[text/html,text/xml](the precondition),.txtserved astext/plain, uploads refuse.html/.svg/.xml/.xhtml, keep refuses a rename to.svgTestBrowserHeaders: CSP, frame protection,nosniffapplication/jsonTestCrossOriginReads: noAccess-Control-Allow-Originor-Credentialsfor a foreign Origin on API, media or UI routes; a foreign preflight is refused; a same-origin request passesTestDownloadNames: quotes,;, RTL override, CR/LF in a file name can't add a header or a secondfilename=; the listing reports hostile names as JSON dataFindings
dw/content_types.pyleavestext/*result types permissive by design. A workflow with"result": {"content_type": "text/html"}validates clean and writes an.htmlfile with whatever the step produced (text/xmlwrites.xml)./outputs(no token) serves it astext/htmlon the UI origin: noContent-Disposition: attachment, no CSP sandbox.target="_blank".document.titletostolen:"secret-probe", the token planted inlocalStorage.output_fileindw/server/app.py..xhtml/.svgare served the same way, but the engine can't write them, so those need a planted file./inputs. High (same root cause).POST /api/assets/keepchecks only that the kept name's extension matches the source's. An.htmloutput becomes an.htmlasset, and/inputsserves it astext/html..svgis refused.Content-Security-Policyon the UI. Medium (defence in depth). Nothing limits what an injected script loads or where it sends the token. A CSP would have contained finding 1.X-Frame-Options/frame-ancestors. Medium. Any site can frame the UI and clickjack Run or Delete. The Origin check doesn't help, because a framed page is same-origin.X-Content-Type-Options: nosniffon the UI or on/outputs. Low: I found no route where sniffing changes the outcome today.Fixing finding 1 (either refuse active types in
content_type_fault, or serve/outputsand/inputswithattachment,nosniffandCSP: sandbox) also covers finding 2. Which one is a design call, so it's yours.Already failing on
developNone. On this branch (merged with current
develop): pytest 5904 passed, 15 skipped, 11 xfailed; vitest 335 passed; the full Playwright suite 108 passed (the 98 existing plus these 10). eslint, prettier and ruff are clean on the new files.CI does not run Playwright. It runs prettier and eslint on
e2e/, not the specs, sosecurity.spec.tsonly runs throughnpm run e2e/npm run preflight. Worth deciding whether e2e belongs in CI. It needs the Python environment and ~2 minutes.Environment note: this sandbox has Chromium build 1194 while Playwright 1.62 expects 1234. I aliased them locally only; nothing about that is committed.
Not covered
?token=on thumbnail, download and SSE URLs lands in server logs and history. It's a documented trade-off, so no test.package.jsonalready pins dompurify for it.🤖 Generated with Claude Code
https://claude.ai/code/session_01NSpbAKgGb282ixhsEqGQ52
Generated by Claude Code