From 9712bb6681a97eda112e873da99020bcb660493a Mon Sep 17 00:00:00 2001 From: Matan Eden <57892946+MatanEden1@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:09:45 +0300 Subject: [PATCH 1/5] AX-1941 - Add-*.jfrog.io-to-workspace-sandbox-allowlist Co-authored-by: Cursor --- .cursor/sandbox.json | 7 +++ scripts/test-sandbox-network.sh | 98 +++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 .cursor/sandbox.json create mode 100755 scripts/test-sandbox-network.sh diff --git a/.cursor/sandbox.json b/.cursor/sandbox.json new file mode 100644 index 0000000..c4e39dc --- /dev/null +++ b/.cursor/sandbox.json @@ -0,0 +1,7 @@ +{ + "networkPolicy": { + "allow": [ + "*.jfrog.io" + ] + } +} diff --git a/scripts/test-sandbox-network.sh b/scripts/test-sandbox-network.sh new file mode 100755 index 0000000..9b6513b --- /dev/null +++ b/scripts/test-sandbox-network.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# Verify that *.jfrog.io is reachable from inside Cursor's Agents Window sandbox. +# +# Run this script from inside the Agents Window after adding .cursor/sandbox.json. +# It probes the same hosts the ticket reporter tested via CONNECT through the +# injected sandbox proxy (HTTP_PROXY/HTTPS_PROXY). +# +# Exit 0 = all hosts behaved as expected (sandbox.json is working, no regressions) +# Exit 1 = one or more hosts failed +# Exit 2 = not actually running inside the sandbox, so the result would be meaningless + +set -euo pipefail + +# Hosts that should be covered by the *.jfrog.io allow entry. +# Only stable JFrog infrastructure hostnames are listed here; ephemeral trial +# instances (e.g. trialjfrogmlv22.jfrog.io) would produce false failures once +# deprovisioned. +JFROG_HOSTS=( + "releases.jfrog.io" + "download.jfrog.io" + "entplus.jfrog.io" +) + +# Hosts the ticket confirmed as already allowed by Cursor's defaults — must stay +# reachable after adding sandbox.json, otherwise the workspace policy regressed them. +CONTROL_HOSTS=( + "registry.npmjs.org" + "pypi.org" + "nodejs.org" +) + +command -v curl >/dev/null 2>&1 || { echo "curl is required but not found"; exit 1; } + +fail=0 + +# The sandbox proxy blocks a CONNECT tunnel by closing the connection, which +# makes curl exit with code 56 (recv failure) and %{http_code} returns "000". +# A legitimate server-side 403 (e.g. auth required) means the CONNECT tunnel +# succeeded and the host IS reachable — treat it as allowed, not blocked. +probe() { + local host="$1" want="$2" code + code=$(curl -s -o /dev/null -w "%{http_code}" \ + --max-time 5 \ + "https://${host}/" 2>/dev/null || echo "000") + + local blocked=0 + [[ "$code" == "000" ]] && blocked=1 + + if [[ "$want" == "allow" ]]; then + if [[ "$blocked" == "1" ]]; then + echo "FAIL ${host} (blocked — expected reachable; code=${code})" + ((fail++)) || true + else + echo "OK ${host} (HTTP ${code})" + fi + else + if [[ "$blocked" == "1" ]]; then + echo "OK ${host} (still blocked, as expected; code=${code})" + else + echo "FAIL ${host} (reachable — expected blocked; code=${code})" + ((fail++)) || true + fi + fi +} + +echo "Sandbox env:" +echo " CURSOR_SANDBOX=${CURSOR_SANDBOX:-}" +echo " HTTP_PROXY=${HTTP_PROXY:-}" +echo "" + +if [[ "${CURSOR_SANDBOX:-}" != "seatbelt" ]]; then + echo "WARNING: CURSOR_SANDBOX is not \"seatbelt\" — this shell is not inside Cursor's" + echo "Agents Window sandbox, so hosts will look reachable regardless of sandbox.json." + echo "Run this from inside the Agents Window to actually verify the fix." + exit 2 +fi + +echo "-- *.jfrog.io hosts (should now be allowed) --" +for host in "${JFROG_HOSTS[@]}"; do + probe "$host" "allow" +done + +echo "" +echo "-- control hosts (should remain allowed by Cursor's defaults) --" +for host in "${CONTROL_HOSTS[@]}"; do + probe "$host" "allow" +done + +echo "" +if [[ $fail -eq 0 ]]; then + echo "All hosts behaved as expected — sandbox.json is working, no regressions." + exit 0 +else + echo "${fail} host(s) failed. If a jfrog.io host is still blocked, check for an" + echo "org-level (team-admin) Cursor network policy — it replaces the workspace/user" + echo "allow-list union rather than merging with it, per Cursor's sandbox docs." + exit 1 +fi From ec9cc23781c03c28b5f58cf4ee57e40480af7f79 Mon Sep 17 00:00:00 2001 From: Matan Eden <57892946+MatanEden1@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:23:25 +0300 Subject: [PATCH 2/5] fix probe(): 403 is reachable not blocked, add HTTPS_PROXY to env output - Revert the 403-as-blocked change: a server-side 403 means the CONNECT tunnel succeeded and the host IS reachable. Only HTTP 000 means blocked. - Use arithmetic (( blocked )) instead of string comparison. - Print HTTPS_PROXY in env diagnostics (all probed URLs are HTTPS). Co-authored-by: Cursor --- scripts/test-sandbox-network.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/test-sandbox-network.sh b/scripts/test-sandbox-network.sh index 9b6513b..03e7b26 100755 --- a/scripts/test-sandbox-network.sh +++ b/scripts/test-sandbox-network.sh @@ -47,14 +47,14 @@ probe() { [[ "$code" == "000" ]] && blocked=1 if [[ "$want" == "allow" ]]; then - if [[ "$blocked" == "1" ]]; then + if (( blocked )); then echo "FAIL ${host} (blocked — expected reachable; code=${code})" ((fail++)) || true else echo "OK ${host} (HTTP ${code})" fi else - if [[ "$blocked" == "1" ]]; then + if (( blocked )); then echo "OK ${host} (still blocked, as expected; code=${code})" else echo "FAIL ${host} (reachable — expected blocked; code=${code})" @@ -66,6 +66,7 @@ probe() { echo "Sandbox env:" echo " CURSOR_SANDBOX=${CURSOR_SANDBOX:-}" echo " HTTP_PROXY=${HTTP_PROXY:-}" +echo " HTTPS_PROXY=${HTTPS_PROXY:-}" echo "" if [[ "${CURSOR_SANDBOX:-}" != "seatbelt" ]]; then From 235ed107ef76ccff044f1133a9483f094a227cca Mon Sep 17 00:00:00 2001 From: Matan Eden <57892946+MatanEden1@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:00:56 +0300 Subject: [PATCH 3/5] AX-1941 - Fix-probe-false-pass-on-blocked-hosts Do not append 000 when curl exits 56; that concatenated to 000000 and treated a CONNECT drop as reachable. Co-authored-by: Cursor --- scripts/test-sandbox-network.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/test-sandbox-network.sh b/scripts/test-sandbox-network.sh index 03e7b26..adeabff 100755 --- a/scripts/test-sandbox-network.sh +++ b/scripts/test-sandbox-network.sh @@ -39,12 +39,14 @@ fail=0 # succeeded and the host IS reachable — treat it as allowed, not blocked. probe() { local host="$1" want="$2" code + # Do not `|| echo "000"`: on CONNECT-close curl already writes http_code 000 + # and exits 56, so that would concatenate to 000000 and look reachable. code=$(curl -s -o /dev/null -w "%{http_code}" \ --max-time 5 \ - "https://${host}/" 2>/dev/null || echo "000") + "https://${host}/" 2>/dev/null) || true local blocked=0 - [[ "$code" == "000" ]] && blocked=1 + [[ -z "$code" || "$code" == "000" ]] && blocked=1 if [[ "$want" == "allow" ]]; then if (( blocked )); then From f8afa87661e489def0b8e04251e1b7bf2820aa1f Mon Sep 17 00:00:00 2001 From: Matan Eden <57892946+MatanEden1@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:34:00 +0300 Subject: [PATCH 4/5] AX-1941 - Apply code review fixes - sandbox.json: add explicit "default": "deny" for clarity - test script: add DENY_HOSTS probe for negative coverage - test script: remove 2>/dev/null so curl errors are visible - test script: add --connect-timeout 3 for fast failure on blocked hosts Co-authored-by: Cursor --- .cursor/sandbox.json | 1 + scripts/test-sandbox-network.sh | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.cursor/sandbox.json b/.cursor/sandbox.json index c4e39dc..f896c2e 100644 --- a/.cursor/sandbox.json +++ b/.cursor/sandbox.json @@ -1,5 +1,6 @@ { "networkPolicy": { + "default": "deny", "allow": [ "*.jfrog.io" ] diff --git a/scripts/test-sandbox-network.sh b/scripts/test-sandbox-network.sh index adeabff..0d37c22 100755 --- a/scripts/test-sandbox-network.sh +++ b/scripts/test-sandbox-network.sh @@ -29,6 +29,12 @@ CONTROL_HOSTS=( "nodejs.org" ) +# Hosts not in *.jfrog.io and not in Cursor's defaults — must remain blocked. +# Confirms the sandbox is actually enforced, not just running unrestricted. +DENY_HOSTS=( + "example.com" +) + command -v curl >/dev/null 2>&1 || { echo "curl is required but not found"; exit 1; } fail=0 @@ -39,11 +45,11 @@ fail=0 # succeeded and the host IS reachable — treat it as allowed, not blocked. probe() { local host="$1" want="$2" code - # Do not `|| echo "000"`: on CONNECT-close curl already writes http_code 000 - # and exits 56, so that would concatenate to 000000 and look reachable. + # Use || true (not || echo "000"): curl already emits "000" on CONNECT-close + # (exit 56). Appending an echo would produce "000000" and defeat the check. code=$(curl -s -o /dev/null -w "%{http_code}" \ - --max-time 5 \ - "https://${host}/" 2>/dev/null) || true + --connect-timeout 3 --max-time 5 \ + "https://${host}/") || true local blocked=0 [[ -z "$code" || "$code" == "000" ]] && blocked=1 @@ -89,6 +95,12 @@ for host in "${CONTROL_HOSTS[@]}"; do probe "$host" "allow" done +echo "" +echo "-- hosts that should remain blocked --" +for host in "${DENY_HOSTS[@]}"; do + probe "$host" "block" +done + echo "" if [[ $fail -eq 0 ]]; then echo "All hosts behaved as expected — sandbox.json is working, no regressions." From f55ca215175933f08fb6b05f45bccc8c4d8ed301 Mon Sep 17 00:00:00 2001 From: Matan Eden <57892946+MatanEden1@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:46:34 +0300 Subject: [PATCH 5/5] AX-1941 - Apply code review fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sandbox.json: remove "default":"deny" — workspace allow entries are unioned with Cursor's defaults; an explicit deny directive risked replacing them - test script: replace example.com deny-hosts with RFC 5737 (203.0.113.1) and RFC 2606 (.invalid TLD) addresses that are structurally guaranteed unreachable, preventing future false failures if Cursor's defaults ever include example.com - test script: add proxy env-vars guard — exit 2 when CURSOR_SANDBOX=seatbelt but neither HTTP_PROXY nor HTTPS_PROXY is set (curl would bypass the proxy, making every host look reachable) - test script: add want-argument validation in probe() to catch typos early - test script: clarify the curl "000" and ((fail++)) || true comments Co-authored-by: Cursor --- .cursor/sandbox.json | 1 - scripts/test-sandbox-network.sh | 24 +++++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.cursor/sandbox.json b/.cursor/sandbox.json index f896c2e..c4e39dc 100644 --- a/.cursor/sandbox.json +++ b/.cursor/sandbox.json @@ -1,6 +1,5 @@ { "networkPolicy": { - "default": "deny", "allow": [ "*.jfrog.io" ] diff --git a/scripts/test-sandbox-network.sh b/scripts/test-sandbox-network.sh index 0d37c22..ce76533 100755 --- a/scripts/test-sandbox-network.sh +++ b/scripts/test-sandbox-network.sh @@ -31,8 +31,13 @@ CONTROL_HOSTS=( # Hosts not in *.jfrog.io and not in Cursor's defaults — must remain blocked. # Confirms the sandbox is actually enforced, not just running unrestricted. +# Use structurally guaranteed-unreachable addresses so that future changes to +# Cursor's default allow-list never cause false failures here: +# 203.0.113.1 — TEST-NET-3 (RFC 5737), not routed on the public internet. +# dns-test.blocked.invalid — .invalid TLD (RFC 2606), never resolvable. DENY_HOSTS=( - "example.com" + "203.0.113.1" + "dns-test.blocked.invalid" ) command -v curl >/dev/null 2>&1 || { echo "curl is required but not found"; exit 1; } @@ -45,8 +50,14 @@ fail=0 # succeeded and the host IS reachable — treat it as allowed, not blocked. probe() { local host="$1" want="$2" code - # Use || true (not || echo "000"): curl already emits "000" on CONNECT-close - # (exit 56). Appending an echo would produce "000000" and defeat the check. + + # Validate the want argument to catch typos early. + [[ "$want" == "allow" || "$want" == "block" ]] \ + || { echo "probe: invalid want='$want' (must be 'allow' or 'block')"; exit 1; } + + # curl writes "000" via -w "%{http_code}" when a transfer fails (e.g. exit 56 + # on CONNECT-close). Using || echo "000" would append a second "000" to the + # captured output, producing "000000" and breaking the string comparison. code=$(curl -s -o /dev/null -w "%{http_code}" \ --connect-timeout 3 --max-time 5 \ "https://${host}/") || true @@ -57,6 +68,7 @@ probe() { if [[ "$want" == "allow" ]]; then if (( blocked )); then echo "FAIL ${host} (blocked — expected reachable; code=${code})" + # (( expr )) returns exit 1 when the result is 0; || true guards set -e. ((fail++)) || true else echo "OK ${host} (HTTP ${code})" @@ -84,6 +96,12 @@ if [[ "${CURSOR_SANDBOX:-}" != "seatbelt" ]]; then exit 2 fi +if [[ -z "${HTTPS_PROXY:-}" && -z "${HTTP_PROXY:-}" ]]; then + echo "WARNING: CURSOR_SANDBOX=seatbelt but neither HTTPS_PROXY nor HTTP_PROXY is set." + echo "curl will bypass the sandbox proxy; results are not meaningful." + exit 2 +fi + echo "-- *.jfrog.io hosts (should now be allowed) --" for host in "${JFROG_HOSTS[@]}"; do probe "$host" "allow"