Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions docs/network-policy/integration-policy-examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -214,16 +214,17 @@ $ 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
```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.
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.
This manual probe proves curl reached Atlassian, but no Jira credentials were supplied.

Remove access when the task is done:

Expand Down
11 changes: 6 additions & 5 deletions src/lib/policy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ");
}

Expand Down
20 changes: 13 additions & 7 deletions test/e2e/test-network-policy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)"
Expand Down
4 changes: 2 additions & 2 deletions test/policies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
Expand Down
Loading