From 878b45ab991c27197c62702f0b38ff818fdd073b Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Sun, 31 May 2026 08:22:38 -0700 Subject: [PATCH 1/3] fix(policy): make Jira curl validation observable --- .../integration-policy-examples.mdx | 10 ++++++---- src/lib/policy/index.ts | 11 +++++----- test/e2e/test-network-policy.sh | 20 ++++++++++++------- test/policies.test.ts | 4 ++-- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/docs/network-policy/integration-policy-examples.mdx b/docs/network-policy/integration-policy-examples.mdx index 601cfcf4e4..fd32e053fd 100644 --- a/docs/network-policy/integration-policy-examples.mdx +++ b/docs/network-policy/integration-policy-examples.mdx @@ -214,16 +214,18 @@ $ nemoclaw my-assistant policy-add jira --yes The `jira` preset intentionally allows Node.js access to Atlassian Cloud and does not allow `curl`. When validating it manually, avoid plain `curl -s` against `auth.atlassian.com`. Atlassian can return an empty redirect body even when the request succeeds. -Use an explicit status probe instead: +Use a body-visible API probe instead: ```console $ node -e "require('https').get('https://api.atlassian.com', r => console.log(r.statusCode))" -$ curl -sS -o /dev/null -w '%{http_code}' --max-time 10 https://auth.atlassian.com +$ curl -sS --max-time 10 -w '\n%{http_code}\n' https://api.atlassian.com/oauth/token/accessible-resources ``` Before approval, the curl probe should report `000` or a local policy denial. -After approving the blocked request in OpenShell, it should report an HTTP -status such as `301` or `200`. +After explicitly approving curl for `api.atlassian.com` in OpenShell, it should +return Atlassian's unauthenticated `401` JSON response. That `401` is the +expected success signal for this manual probe: it proves curl reached +Atlassian, but no Jira credentials were supplied. Remove access when the task is done: diff --git a/src/lib/policy/index.ts b/src/lib/policy/index.ts index e7218398de..eaf46cf9b2 100644 --- a/src/lib/policy/index.ts +++ b/src/lib/policy/index.ts @@ -221,11 +221,12 @@ function getPresetValidationWarning(presetName: string): string | null { 'node -e "require(\'https\').get(\'https://api.atlassian.com\', r => console.log(r.statusCode))"', "curl is intentionally not in the preset binary allowlist. Avoid plain", "curl -s probes for auth.atlassian.com: Atlassian can return an empty", - "redirect body, which looks the same as a blocked request. Use an", - "observable status probe instead:", - "curl -sS -o /dev/null -w '%{http_code}' --max-time 10 https://auth.atlassian.com", - "Before approval, expect 000 or a local policy denial; after approval,", - "expect an HTTP status such as 301 or 200.", + "redirect body, which looks the same as a blocked request. Use a", + "body-visible API probe instead:", + "curl -sS --max-time 10 -w '\\n%{http_code}\\n' https://api.atlassian.com/oauth/token/accessible-resources", + "Before approval, expect 000 or a local policy denial. After explicitly", + "approving curl for api.atlassian.com, expect Atlassian's 401 JSON", + "response, which proves curl reached the service without Jira credentials.", ].join("\n "); } diff --git a/test/e2e/test-network-policy.sh b/test/e2e/test-network-policy.sh index 3adf2dcbf5..0f8cddf756 100755 --- a/test/e2e/test-network-policy.sh +++ b/test/e2e/test-network-policy.sh @@ -527,6 +527,7 @@ fetch('$target_url', {signal: AbortSignal.timeout(15000)}) # ============================================================================= test_net_08_jira_per_binary_enforcement() { log "=== TC-NET-08: Jira Per-Binary Policy Enforcement ===" + local curl_probe_url="https://api.atlassian.com/oauth/token/accessible-resources" log " Step 1: Applying jira preset..." if ! apply_preset "jira"; then @@ -563,13 +564,14 @@ req.on('error', (error) => console.log('NODE_ERROR_' + (error.code || error.mess log " Step 3: Verify curl remains blocked by the Jira preset..." local curl_before curl_before=$(sandbox_exec "set +e -OUT=\$(curl -sS -o /dev/null -w 'CURL_STATUS_%{http_code} CURL_APPCONNECT_%{time_appconnect}' --max-time 10 https://auth.atlassian.com 2>&1) +OUT=\$(curl -sS -o /dev/null -w 'CURL_STATUS_%{http_code} CURL_APPCONNECT_%{time_appconnect}' --max-time 10 ${curl_probe_url} 2>&1) RC=\$? echo \"\$OUT CURL_RC_\$RC\" " 2>&1) || true log " Curl before explicit approval: $curl_before" - if echo "$curl_before" | grep -qE "CURL_STATUS_[23][0-9][0-9]"; then + if echo "$curl_before" | grep -qE "CURL_STATUS_[1-9][0-9][0-9]" \ + && ! echo "$curl_before" | grep -qE "CURL_STATUS_403.*CURL_APPCONNECT_0(\.0+)?( |$)"; then fail "TC-NET-08: Curl pre-approval" "curl reached Atlassian without explicit approval ($curl_before)" return elif echo "$curl_before" | grep -qE "CURL_STATUS_000|CURL_STATUS_403|CURL_RC_[1-9]|denied|policy|forbidden"; then @@ -584,9 +586,9 @@ echo \"\$OUT CURL_RC_\$RC\" return fi - log " Step 4: Explicitly allow curl to auth.atlassian.com via OpenShell policy update..." + log " Step 4: Explicitly allow curl to api.atlassian.com via OpenShell policy update..." if ! openshell policy update "$SANDBOX_NAME" \ - --add-endpoint auth.atlassian.com:443:read-only:rest:enforce \ + --add-endpoint api.atlassian.com:443:read-only:rest:enforce \ --binary /usr/bin/curl \ --binary /usr/local/bin/curl \ --wait 2>&1 | tee -a "$LOG_FILE"; then @@ -598,13 +600,17 @@ echo \"\$OUT CURL_RC_\$RC\" log " Step 5: Verify curl reaches Atlassian after explicit approval..." local curl_after curl_after=$(sandbox_exec "set +e -OUT=\$(curl -sS -o /dev/null -w 'CURL_STATUS_%{http_code}' --max-time 10 https://auth.atlassian.com 2>&1) +rm -f /tmp/nemoclaw-jira-curl-body +OUT=\$(curl -sS -o /tmp/nemoclaw-jira-curl-body -w 'CURL_STATUS_%{http_code}' --max-time 10 ${curl_probe_url} 2>&1) RC=\$? -echo \"\$OUT CURL_RC_\$RC\" +printf '%s CURL_RC_%s CURL_BODY_' \"\$OUT\" \"\$RC\" +head -c 120 /tmp/nemoclaw-jira-curl-body 2>/dev/null || true +printf '\n' " 2>&1) || true log " Curl after explicit approval: $curl_after" - if echo "$curl_after" | grep -qE "CURL_STATUS_[23][0-9][0-9]"; then + if echo "$curl_after" | grep -qE "CURL_STATUS_401" \ + && echo "$curl_after" | grep -qE "Unauthorized|unauthorized"; then pass "TC-NET-08: curl reaches Atlassian after explicit approval ($curl_after)" else fail "TC-NET-08: Curl post-approval" "curl did not reach Atlassian after explicit approval ($curl_after)" diff --git a/test/policies.test.ts b/test/policies.test.ts index efafe5b48b..e462684952 100644 --- a/test/policies.test.ts +++ b/test/policies.test.ts @@ -493,8 +493,8 @@ describe("policies", () => { const warning = policies.getPresetValidationWarning("jira"); expect(warning).toContain("curl -s"); - expect(warning).toContain("curl -sS -o /dev/null -w '%{http_code}'"); - expect(warning).toContain("000"); + expect(warning).toContain("api.atlassian.com/oauth/token/accessible-resources"); + expect(warning).toContain("401 JSON"); expect(warning).toContain("Node HTTPS"); expect(warning).toContain("https://api.atlassian.com"); }); From c15111a349c2a21a7a8bd0d8842d0f14a7dd84d0 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Sun, 31 May 2026 08:40:38 -0700 Subject: [PATCH 2/3] docs(policy): make Jira validation probe copyable --- docs/network-policy/integration-policy-examples.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/network-policy/integration-policy-examples.mdx b/docs/network-policy/integration-policy-examples.mdx index fd32e053fd..f61a23b1f4 100644 --- a/docs/network-policy/integration-policy-examples.mdx +++ b/docs/network-policy/integration-policy-examples.mdx @@ -216,9 +216,9 @@ When validating it manually, avoid plain `curl -s` against `auth.atlassian.com`. Atlassian can return an empty redirect body even when the request succeeds. Use a body-visible API probe instead: -```console -$ node -e "require('https').get('https://api.atlassian.com', r => console.log(r.statusCode))" -$ curl -sS --max-time 10 -w '\n%{http_code}\n' https://api.atlassian.com/oauth/token/accessible-resources +```bash +node -e "require('https').get('https://api.atlassian.com', r => console.log(r.statusCode))" +curl -sS --max-time 10 -w '\n%{http_code}\n' https://api.atlassian.com/oauth/token/accessible-resources ``` Before approval, the curl probe should report `000` or a local policy denial. From 8b90b63fb70637f2965cd390bd0df3179fb330dd Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Sun, 31 May 2026 08:41:53 -0700 Subject: [PATCH 3/3] docs(policy): align Jira probe guidance with docs style --- docs/network-policy/integration-policy-examples.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/network-policy/integration-policy-examples.mdx b/docs/network-policy/integration-policy-examples.mdx index f61a23b1f4..afb16a9ed2 100644 --- a/docs/network-policy/integration-policy-examples.mdx +++ b/docs/network-policy/integration-policy-examples.mdx @@ -222,10 +222,9 @@ curl -sS --max-time 10 -w '\n%{http_code}\n' https://api.atlassian.com/oauth/tok ``` Before approval, the curl probe should report `000` or a local policy denial. -After explicitly approving curl for `api.atlassian.com` in OpenShell, it should -return Atlassian's unauthenticated `401` JSON response. That `401` is the -expected success signal for this manual probe: it proves curl reached -Atlassian, but no Jira credentials were supplied. +After explicitly approving curl for `api.atlassian.com` in OpenShell, it should return Atlassian's unauthenticated `401` JSON response. +That `401` is the expected success signal for this manual probe. +This manual probe proves curl reached Atlassian, but no Jira credentials were supplied. Remove access when the task is done: