Skip to content

Block and report CSP violations for javascript: URLs in window.open#40997

Merged
TimvdLippe merged 1 commit intoservo:mainfrom
dyegoaurelio:fix-js-window-blocked-test
Dec 2, 2025
Merged

Block and report CSP violations for javascript: URLs in window.open#40997
TimvdLippe merged 1 commit intoservo:mainfrom
dyegoaurelio:fix-js-window-blocked-test

Conversation

@dyegoaurelio
Copy link
Copy Markdown
Contributor

@dyegoaurelio dyegoaurelio commented Dec 2, 2025

Previously, when window.open() was called with a javascript: URL, the script would execute unconditionally without any CSP enforcement. The CSP list was not propagated from the opener to the newly opened document.

This commit fixes two issues:

  1. Propagate CSP from opener to new document The CSP list from the existing (opener) document is now copied to the target document before navigation, ensuring CSP policies are enforced.

  2. Report CSP violations to the correct window Per the CSP spec, violations should be reported to the navigation request's client (the opener window), not the target window. To achieve this, we check CSP early in window.open() while both windows are accessible, before entering the normal navigation flow where only the target window is available.

Testing: new wpt successes:

  • content-security-policy/script-src/javascript-window-open-blocked.html
  • content-security-policy/unsafe-hashes/javascript_src_denied_missing_unsafe_hashes-window_open.html
  • content-security-policy/unsafe-hashes/javascript_src_denied_wrong_hash-window_open.html

Fixes: part of #36437

…w.open

Previously, when window.open() was called with a javascript: URL, the script
would execute unconditionally without any CSP enforcement. The CSP list was
not propagated from the opener to the newly opened document.

This commit fixes two issues:

1. Propagate CSP from opener to new document
   The CSP list from the existing (opener) document is now copied to the
   target document before navigation, ensuring CSP policies are enforced.

2. Report CSP violations to the correct window
   Per the CSP spec, violations should be reported to the navigation
   request's client (the opener window), not the target window. To achieve
   this, we check CSP early in window.open() while both windows are
   accessible, before entering the normal navigation flow where only the
   target window is available.

Signed-off-by: Dyego Aurélio <[email protected]>
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Dec 2, 2025
Copy link
Copy Markdown
Contributor

@TimvdLippe TimvdLippe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix!

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Dec 2, 2025
@TimvdLippe TimvdLippe added the T-linux-wpt Do a try run of the WPT label Dec 2, 2025
@github-actions github-actions bot removed the T-linux-wpt Do a try run of the WPT label Dec 2, 2025
@TimvdLippe TimvdLippe added this pull request to the merge queue Dec 2, 2025
@github-actions
Copy link
Copy Markdown

github-actions bot commented Dec 2, 2025

🔨 Triggering try run (#19849342837) for Linux (WPT)

@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Dec 2, 2025
@github-actions
Copy link
Copy Markdown

github-actions bot commented Dec 2, 2025

Test results for linux-wpt from try job (#19849342837):

Flaky unexpected result (42)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • TIMEOUT [expected PASS] subtest: Fetching a blob URL immediately before revoking it works in &lt;script&gt; tags.

      Test timed out
      

  • OK /FileAPI/url/url-with-fetch.any.worker.html (#21517)
    • FAIL [expected PASS] subtest: Revoke blob URL after calling fetch, fetch should succeed

      promise_test: Unhandled rejection with value: object "TypeError: Network error occurred"
      

  • OK /IndexedDB/idbfactory_open.any.html
    • FAIL [expected PASS] subtest: Calling open() with version argument 1.5 should not throw.

      assert_equals: version expected 1 but got 9007199254740991
      

  • PASS [expected FAIL] /_mozilla/css/linear_gradients_reverse_a.html
  • OK /_mozilla/webxr/create_session.https.html
    • FAIL [expected PASS] subtest: create_session

      can't access property "simulateDeviceConnection", navigator.xr.test is undefined
      

  • OK /_mozilla/webxr/obtain_frame.https.html
    • FAIL [expected PASS] subtest: obtain_frame

      promise_test: Unhandled rejection with value: object "TypeError: can't access property "simulateDeviceConnection", navigator.xr.test is undefined"
      

  • ERROR [expected TIMEOUT] /_mozilla/webxr/sessionavailable.https.html
  • TIMEOUT [expected ERROR] /_webgl/conformance/extensions/oes-texture-float-with-canvas.html
  • CRASH [expected OK] /_webgl/conformance/more/functions/vertexAttribBadArgs.html
  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • PASS [expected FAIL] subtest: WebGL test #45
    • PASS [expected FAIL] subtest: WebGL test #47
    • PASS [expected FAIL] subtest: WebGL test #49
    • PASS [expected FAIL] subtest: WebGL test #51
    • FAIL [expected PASS] subtest: WebGL test #53

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #55

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #57

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #59

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • PASS [expected FAIL] subtest: WebGL test #61
    • PASS [expected FAIL] subtest: WebGL test #63
    • And 6 more unexpected results...
  • CRASH [expected OK] /_webgl/conformance2/buffers/buffer-overflow-test.html
  • TIMEOUT /content-security-policy/inheritance/location-reload.html (#38983)
    • FAIL [expected PASS] subtest: location.reload() of empty iframe.

      assert_equals: Image should be blocked by CSP after reload. expected "img blocked" but got "img loaded"
      

  • TIMEOUT [expected OK] /credential-management/credentialscontainer-frame-basics.https.html (#39430)
    • TIMEOUT [expected FAIL] subtest: navigator.credentials should be undefined in documents generated from data: URLs.

      Test timed out
      

  • FAIL [expected PASS] /css/css-backgrounds/background-size-041.html
  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted cursive (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(kai) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-serif (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-sans-serif (drawing text in a canvas)
  • OK /custom-elements/form-associated/ElementInternals-setFormValue.html (#29174)
    • PASS [expected FAIL] subtest: Single value - empty name exists
    • PASS [expected FAIL] subtest: Null value should submit nothing
  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Cross-site

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • TIMEOUT [expected CRASH] /html/anonymous-iframe/indexeddb.tentative.https.window.html (#39254)
  • CRASH [expected OK] /html/anonymous-iframe/local-storage.tentative.https.window.html
  • CRASH [expected OK] /html/browsers/browsing-the-web/back-forward-cache/storage-events.html
  • CRASH [expected OK] /html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-navigation-cross-origin.html
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment.html (#20768)
    • PASS [expected FAIL] subtest: Tests that a fragment navigation in the unload handler will not block the initial navigation
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.html (#29049)
    • PASS [expected FAIL] subtest: Same-origin navigation started from unload handler must be ignored
  • OK /html/browsers/history/the-history-interface/traverse_the_history_3.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • OK /html/browsers/history/the-history-interface/traverse_the_history_4.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • OK /html/browsers/windows/browsing-context-names/duplicate-name-order.html (#34623)
    • PASS [expected FAIL] subtest: Duplicate name lookup order
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/update-the-rendering.html (#24145)
    • TIMEOUT [expected FAIL] subtest: "Flush autofocus candidates" should be happen before a scroll event and animation frame callbacks

      Test timed out
      

  • OK /html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-2.html (#39703)
    • FAIL [expected PASS] subtest: Meta refresh of the original iframe is not blocked if moved into a sandboxed iframe

      uncaught exception: Error: assert_unreached: The iframe into which the meta was moved must not refresh Reached unreachable code
      

  • TIMEOUT [expected OK] /html/semantics/embedded-content/media-elements/readyState_during_loadeddata.html
  • OK /html/semantics/embedded-content/media-elements/seeking/seek-to-max-value.htm (#40626)
    • FAIL [expected PASS] subtest: seek to Number.MAX_VALUE

      assert_equals: currentTime after setting expected 5.153333333 but got 0
      

  • OK /html/semantics/forms/form-submission-0/multipart-formdata.window.html (#28725)
    • PASS [expected FAIL] subtest: multipart/form-data: 0x00 in value (normal form)
  • OK /html/semantics/forms/form-submission-0/text-plain.window.html (#28687)
    • FAIL [expected PASS] subtest: text/plain: Basic test (formdata event)

      assert_equals: expected "basic=test\r\n" but got ""
      

    • FAIL [expected PASS] subtest: text/plain: Basic File test (normal form)

      assert_equals: expected "basic=file-test.txt\r\n" but got ""
      

  • OK /html/semantics/forms/form-submission-0/urlencoded2.window.html (#28687)
    • FAIL [expected PASS] subtest: application/x-www-form-urlencoded: Basic test (formdata event)

      assert_equals: expected "basic=test" but got ""
      

    • FAIL [expected PASS] subtest: application/x-www-form-urlencoded: Basic File test (normal form)

      assert_equals: expected "basic=file-test.txt" but got ""
      

    • PASS [expected FAIL] subtest: application/x-www-form-urlencoded: 0x00 in name (normal form)
    • PASS [expected FAIL] subtest: application/x-www-form-urlencoded: 0x00 in value (formdata event)
    • PASS [expected FAIL] subtest: application/x-www-form-urlencoded: single quote in name (normal form)
  • CRASH [expected OK] /html/webappapis/scripting/reporterror.any.worker.html
  • OK /navigation-timing/test-navigation-type-reload.html (#33334)
    • PASS [expected FAIL] subtest: Reload domComplete &gt; Original domComplete
    • PASS [expected FAIL] subtest: Reload domContentLoadedEventStart &gt; Original domContentLoadedEventStart
    • PASS [expected FAIL] subtest: Reload domInteractive &gt; Original domInteractive
    • PASS [expected FAIL] subtest: Reload fetchStart &gt; Original fetchStart
    • PASS [expected FAIL] subtest: Reload loadEventEnd &gt; Original loadEventEnd
    • PASS [expected FAIL] subtest: Reload loadEventStart &gt; Original loadEventStart
  • OK /preload/preload-error.sub.html (#37177)
    • PASS [expected FAIL] subtest: 404 (fetch): main
    • FAIL [expected PASS] subtest: CORS (fetch): main

      assert_greater_than: http://not-web-platform.test:8000/preload/resources/dummy.xml?pipe=header%28Access-Control-Allow-Origin%2C*%29&amp;label=fetch should be loaded expected a number greater than 0 but got 0
      

  • CRASH [expected ERROR] /trusted-types/SharedWorker-setTimeout-setInterval.html
  • CRASH [expected OK] /trusted-types/eval-csp-no-tt.html
  • CRASH [expected OK] /trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-001.html
  • TIMEOUT /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • TIMEOUT [expected FAIL] subtest: Navigate a frame via anchor with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Navigate a frame via anchor with javascript:-urls w/ default policy in report-only mode.
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • PASS [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.
    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ a default policy throwing an exception in enforcing mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ a default policy throwing an exception in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ a default policy making the URL invalid in enforcing mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

  • TIMEOUT [expected OK] /webstorage/storage_builtins.window.html
Stable unexpected results that are known to be intermittent (17)
  • OK /IndexedDB/idbobjectstore_getAll.any.html (#39276)
    • PASS [expected FAIL] subtest: Get all values with transaction.commit()
  • FAIL [expected PASS] /_mozilla/mozilla/sslfail.html (#10760)
  • TIMEOUT [expected OK] /_mozilla/mozilla/window_resize_event.html (#36741)
    • TIMEOUT [expected PASS] subtest: Popup onresize event fires after resizeTo

      Test timed out
      

  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(fangsong)

      assert_equals: quoted generic(fangsong) matches  @font-face rule expected 50 but got 30
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(khmer-mul)

      assert_equals: quoted generic(khmer-mul) matches  @font-face rule expected 50 but got 30
      

  • OK /css/css-fonts/generic-family-keywords-002.html (#40929)
    • PASS [expected FAIL] subtest: font-family: -webkit-serif treated as &lt;font-family&gt;, not &lt;generic-name&gt;
    • PASS [expected FAIL] subtest: font-family: -webkit-sans-serif treated as &lt;font-family&gt;, not &lt;generic-name&gt;
    • PASS [expected FAIL] subtest: font-family: -webkit-cursive treated as &lt;font-family&gt;, not &lt;generic-name&gt;
    • PASS [expected FAIL] subtest: font-family: -webkit-fantasy treated as &lt;font-family&gt;, not &lt;generic-name&gt;
    • PASS [expected FAIL] subtest: font-family: -webkit-monospace treated as &lt;font-family&gt;, not &lt;generic-name&gt;
    • PASS [expected FAIL] subtest: font-family: -webkit-system-ui treated as &lt;font-family&gt;, not &lt;generic-name&gt;
    • PASS [expected FAIL] subtest: font-family: -webkit-math treated as &lt;font-family&gt;, not &lt;generic-name&gt;
    • FAIL [expected PASS] subtest: font-family: -webkit-generic(fangsong) treated as &lt;font-family&gt;, not &lt;generic-name&gt;

      assert_equals: expected 50 but got 30
      

    • FAIL [expected PASS] subtest: font-family: -webkit-generic(kai) treated as &lt;font-family&gt;, not &lt;generic-name&gt;

      assert_equals: expected 50 but got 30
      

    • FAIL [expected PASS] subtest: font-family: -webkit-generic(khmer-mul) treated as &lt;font-family&gt;, not &lt;generic-name&gt;

      assert_equals: expected 50 but got 30
      

    • And 12 more unexpected results...
  • OK /fetch/metadata/generated/css-font-face.sub.tentative.html (#34624)
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-site destination

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination
  • OK /fetch/metadata/generated/element-img-environment-change.https.sub.html (#30111)
    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Origin -&gt; Cross-Site, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-site - Same-Site -&gt; Same Origin, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Site -&gt; Same-Site, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-mode - attributes: crossorigin
    • FAIL [expected PASS] subtest: sec-fetch-mode - attributes: crossorigin=anonymous

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-mode - attributes: crossorigin=use-credentials
    • PASS [expected FAIL] subtest: sec-fetch-user - no attributes
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Same site, no attributes
  • OK /fetch/metadata/generated/element-img-environment-change.sub.html (#30111)
    • PASS [expected FAIL] subtest: sec-fetch-site - Not sent to non-trustworthy cross-site destination, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-mode - Not sent to non-trustworthy cross-site destination, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-dest - Not sent to non-trustworthy cross-site destination, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • ERROR /fetch/metadata/generated/serviceworker.https.sub.html (#36247)
    • FAIL [expected PASS] subtest: sec-fetch-site - Same origin, no options - registration

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html (#28691)
    • FAIL [expected PASS] subtest: load event does not fire on window.open('about:blank')

      assert_unreached: load should not be fired Reached unreachable code
      

  • OK /html/browsers/history/the-history-interface/traverse_the_history_5.html (#21383)
    • FAIL [expected PASS] subtest: Multiple history traversals, last would be aborted

      assert_array_equals: Pages opened during history navigation expected property 1 to be 5 but got 3 (expected array [6, 5] got [6, 3])
      

  • OK /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • PASS [expected NOTRUN] subtest: Non-HTMLElement should not support autofocus
  • OK /html/semantics/embedded-content/media-elements/preserves-pitch.html (#40352)
    • FAIL [expected PASS] subtest: Speed-ups should not change the pitch when preservesPitch=true

      assert_approx_equals: The actual pitch should be close to the expected pitch. expected 440 +/- 66 but got 0
      

  • OK [expected ERROR] /html/user-activation/no-activation-thru-escape-key.html (#40343)
  • OK /preload/preload-xhr.html (#39092)
    • FAIL [expected PASS] subtest: Make an XHR request immediately after creating link rel=preload.

      assert_equals: resources/dummy.xml?token=8f65e2f9-1c96-4eca-8e88-93b24cd910b1 expected 1 but got 0
      

  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?26-30 (#38807)
    • PASS [expected TIMEOUT] subtest: Navigate a window via form-submission with javascript:-urls in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ default policy in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls in enforcing mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in enforcing mode.
  • OK [expected ERROR] /webxr/render_state_update.https.html (#27535)

@github-actions
Copy link
Copy Markdown

github-actions bot commented Dec 2, 2025

✨ Try run (#19849342837) succeeded.

Merged via the queue into servo:main with commit 194875e Dec 2, 2025
69 checks passed
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Dec 2, 2025
@dyegoaurelio dyegoaurelio deleted the fix-js-window-blocked-test branch December 2, 2025 10:13
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.

4 participants