From 21b36107afeca474d9c5d688a8e7cfc9b8b386e7 Mon Sep 17 00:00:00 2001 From: LeoLin990405 Date: Tue, 7 Jul 2026 14:54:52 +0800 Subject: [PATCH] fix(bridge): bypass Codex page CSP for injected scripts Injected bridge and user scripts run in the Codex page context, so Codex's Content-Security-Policy connect-src blocks their requests to the local helper (http://127.0.0.1:57321) and to plugin endpoints. This surfaces as 'The action has been blocked' when installing from the plugin marketplace and as repeated connect-src violations in the console/logs. Send Page.setBypassCSP { enabled: true } right after Runtime.enable during bridge install, mirroring the existing allowUnsafeEvalBlockedByCSP relaxation already applied to Runtime.evaluate. Renumbers the fixed CDP command ids and updates the sequence assertions; adds a regression test asserting the bypass is sent at id 2 with enabled=true. Fixes #1315 --- crates/codex-plus-core/src/bridge.rs | 18 ++++++-- crates/codex-plus-core/tests/cdp_bridge.rs | 52 ++++++++++++++++++---- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/crates/codex-plus-core/src/bridge.rs b/crates/codex-plus-core/src/bridge.rs index 52ea388ec..552f5d207 100644 --- a/crates/codex-plus-core/src/bridge.rs +++ b/crates/codex-plus-core/src/bridge.rs @@ -114,24 +114,34 @@ pub async fn install_bridge( let mut session = CdpSession::new(socket).with_handler(handler); session.send_command(1, "Runtime.enable", json!({})).await?; + // Disable the Codex page's Content-Security-Policy for injected code. The + // bridge and user scripts run in the page context, so Codex's `connect-src` + // policy otherwise blocks their requests to the local helper + // (http://127.0.0.1:57321) and to plugin endpoints, surfacing as + // "The action has been blocked" during plugin marketplace install. This + // mirrors the existing `allowUnsafeEvalBlockedByCSP` relaxation used for + // Runtime.evaluate. See issue #1315. session - .send_command(2, "Runtime.removeBinding", json!({ "name": binding_name })) + .send_command(2, "Page.setBypassCSP", json!({ "enabled": true })) .await?; session - .send_command(3, "Runtime.addBinding", json!({ "name": binding_name })) + .send_command(3, "Runtime.removeBinding", json!({ "name": binding_name })) + .await?; + session + .send_command(4, "Runtime.addBinding", json!({ "name": binding_name })) .await?; let bridge_script = build_bridge_script(binding_name); session .send_command( - 4, + 5, "Page.addScriptToEvaluateOnNewDocument", json!({ "source": bridge_script }), ) .await?; session .send_command( - 5, + 6, "Runtime.evaluate", runtime_evaluate_params(&bridge_script), ) diff --git a/crates/codex-plus-core/tests/cdp_bridge.rs b/crates/codex-plus-core/tests/cdp_bridge.rs index 96d7e22ae..b7f6257c6 100644 --- a/crates/codex-plus-core/tests/cdp_bridge.rs +++ b/crates/codex-plus-core/tests/cdp_bridge.rs @@ -1208,14 +1208,14 @@ async fn install_bridge_routes_binding_while_waiting_for_command_response() { let log_path = temp.path().join("codex-plus.log"); codex_plus_core::diagnostic_log::set_diagnostic_log_path_for_tests(Some(log_path.clone())); let (url, request_rx) = spawn_cdp_server(|mut socket| async move { - for expected_id in 1..=4 { + for expected_id in 1..=5 { let command = recv_json(&mut socket).await; assert_eq!(command["id"], expected_id); send_json(&mut socket, json!({ "id": expected_id, "result": {} })).await; } let evaluate = recv_json(&mut socket).await; - assert_eq!(evaluate["id"], 5); + assert_eq!(evaluate["id"], 6); assert_eq!(evaluate["method"], "Runtime.evaluate"); send_json( &mut socket, @@ -1231,7 +1231,7 @@ async fn install_bridge_routes_binding_while_waiting_for_command_response() { }), ) .await; - send_json(&mut socket, json!({ "id": 5, "result": {} })).await; + send_json(&mut socket, json!({ "id": 6, "result": {} })).await; let response = recv_json(&mut socket).await; assert_eq!(response["method"], "Runtime.evaluate"); @@ -1278,10 +1278,46 @@ async fn install_bridge_routes_binding_while_waiting_for_command_response() { codex_plus_core::diagnostic_log::set_diagnostic_log_path_for_tests(None); } +#[tokio::test] +async fn install_bridge_bypasses_page_csp_after_enabling_runtime() { + let (url, request_rx) = spawn_cdp_server(|mut socket| async move { + let enable = recv_json(&mut socket).await; + assert_eq!(enable["id"], 1); + assert_eq!(enable["method"], "Runtime.enable"); + send_json(&mut socket, json!({ "id": 1, "result": {} })).await; + + let bypass = recv_json(&mut socket).await; + assert_eq!(bypass["id"], 2); + assert_eq!(bypass["method"], "Page.setBypassCSP"); + assert_eq!(bypass["params"]["enabled"], true); + send_json(&mut socket, json!({ "id": 2, "result": {} })).await; + + for expected_id in 3..=6 { + let command = recv_json(&mut socket).await; + assert_eq!(command["id"], expected_id); + send_json(&mut socket, json!({ "id": expected_id, "result": {} })).await; + } + + close_socket(&mut socket).await; + }) + .await; + + tokio::time::timeout( + Duration::from_secs(2), + bridge::install_bridge(&url, BRIDGE_BINDING_NAME, noop_handler(), &[]), + ) + .await + .expect("bridge should not hang while bypassing page CSP") + .expect("bridge should send Page.setBypassCSP during install"); + request_rx + .await + .expect("server task should finish without panicking"); +} + #[tokio::test] async fn install_bridge_immediately_evaluates_new_document_scripts() { let (url, request_rx) = spawn_cdp_server(|mut socket| async move { - for expected_id in 1..=5 { + for expected_id in 1..=6 { let command = recv_json(&mut socket).await; assert_eq!(command["id"], expected_id); send_json(&mut socket, json!({ "id": expected_id, "result": {} })).await; @@ -1340,7 +1376,7 @@ async fn install_bridge_immediately_evaluates_new_document_scripts() { #[tokio::test] async fn install_bridge_returns_after_installing_and_keeps_message_pump_alive() { let (url, request_rx) = spawn_cdp_server(|mut socket| async move { - for expected_id in 1..=5 { + for expected_id in 1..=6 { let command = recv_json(&mut socket).await; assert_eq!(command["id"], expected_id); send_json(&mut socket, json!({ "id": expected_id, "result": {} })).await; @@ -1458,7 +1494,7 @@ async fn install_bridge_command_error_mentions_method_and_id() { #[tokio::test] async fn install_bridge_rejects_bad_payload_with_id_and_continues_after_unparseable_payload() { let (url, request_rx) = spawn_cdp_server(|mut socket| async move { - for expected_id in 1..=5 { + for expected_id in 1..=6 { let command = recv_json(&mut socket).await; assert_eq!(command["id"], expected_id); send_json(&mut socket, json!({ "id": expected_id, "result": {} })).await; @@ -1543,7 +1579,7 @@ async fn install_bridge_rejects_bad_payload_with_id_and_continues_after_unparsea #[tokio::test] async fn install_bridge_queues_consecutive_bindings_without_recursive_dispatch() { let (url, request_rx) = spawn_cdp_server(|mut socket| async move { - for expected_id in 1..=5 { + for expected_id in 1..=6 { let command = recv_json(&mut socket).await; assert_eq!(command["id"], expected_id); send_json(&mut socket, json!({ "id": expected_id, "result": {} })).await; @@ -1604,7 +1640,7 @@ async fn install_bridge_queues_consecutive_bindings_without_recursive_dispatch() #[tokio::test] async fn install_bridge_does_not_wait_for_resolve_runtime_evaluate_ack() { let (url, request_rx) = spawn_cdp_server(|mut socket| async move { - for expected_id in 1..=5 { + for expected_id in 1..=6 { let command = recv_json(&mut socket).await; assert_eq!(command["id"], expected_id); send_json(&mut socket, json!({ "id": expected_id, "result": {} })).await;