Skip to content

test(security): add SSRF validation edge-case coverage#898

Merged
cv merged 7 commits intoNVIDIA:mainfrom
areporeporepo:test/ssrf-validation-edge-cases
Apr 7, 2026
Merged

test(security): add SSRF validation edge-case coverage#898
cv merged 7 commits intoNVIDIA:mainfrom
areporeporepo:test/ssrf-validation-edge-cases

Conversation

@areporeporepo
Copy link
Copy Markdown
Contributor

@areporeporepo areporeporepo commented Mar 25, 2026

Adds 31 new test cases to ssrf.test.ts covering:

  • CIDR boundary precision for all private ranges
  • IPv6 edge cases: link-local, multicast, fd00::/8
  • IPv4-mapped IPv6 boundary tests
  • DNS rebinding: mixed public+private A records
  • URL parsing: data: URI, query params, userinfo

Signed-off-by: Anh Quang Nguyen anh.nqqq@icloud.com

Summary by CodeRabbit

  • Tests

    • Expanded test coverage for SSRF protections: added IPv4/IPv6 boundary and edge-case assertions (including IPv4-mapped IPv6), DNS rebinding scenarios that reject mixed private resolutions, and URL parsing checks to reject disallowed schemes while accepting valid query/fragment/userinfo cases and preserving original strings.
  • Bug Fixes

    • Tightened private IPv6 classification used during endpoint validation to more accurately detect and block internal addresses.

- CIDR boundary precision for all private ranges
- IPv6 edge cases: link-local, multicast, fd00::/8
- IPv4-mapped IPv6 boundary tests
- DNS rebinding: mixed public+private A records
- URL parsing: data: URI, query params, userinfo

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 25, 2026 18:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Updated SSRF protections: expanded test coverage for IPv4/IPv6 boundaries, DNS rebinding, IPv4-mapped IPv6, and URL parsing; and adjusted IPv6 private-network CIDRs used by isPrivateIp() to refine which IPv6 addresses are classified as private.

Changes

Cohort / File(s) Summary
SSRF Tests
nemoclaw/src/blueprint/ssrf.test.ts
Added ~115 lines of tests: precise isPrivateIp assertions for IPv4 CIDR boundaries, IPv6 edge cases (link-local, multicast, unspecified, ULA boundaries), IPv4‑mapped IPv6 handling, validateEndpointUrl DNS rebinding scenarios (mixed public/private resolutions), and URL parsing edge-case checks (reject data: URIs; accept queries, fragments, userinfo; preserve original URL).
SSRF Logic
nemoclaw/src/blueprint/ssrf.ts
Replaced the previous single IPv6 ULA entry (fd00::/8) with multiple IPv6 CIDRs: ::/128, fc00::/7, fe80::/10, and ff00::/8, changing which IPv6 addresses isPrivateIp() treats as private/internal.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through CIDR leaves and DNS springs,
Sniffed mapped v6 and the private things,
I nibbled bad data: and left good strings to play,
Tests now guard the tunnels where byte-rabbits stray.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding comprehensive SSRF validation edge-case test coverage with 31 new test cases across multiple security scenarios.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds expanded SSRF-related unit test coverage for IP classification and endpoint URL validation in the NemoClaw blueprint SSRF guardrails.

Changes:

  • Add CIDR boundary tests for IPv4 private ranges (10/8, 172.16/12, 169.254/16, 192.168/16 boundaries).
  • Add IPv6 edge-case tests (ULA boundaries and IPv4-mapped IPv6 cases).
  • Add validateEndpointUrl tests for DNS rebinding (mixed A/AAAA results) and URL parsing cases (data: URI, query/fragment, userinfo).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +204 to +208
// link-local and multicast are NOT in PRIVATE_NETWORKS — verify they're treated as public
["fe80::1", false],
["ff02::1", false],
// zero address
["::0", false],
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The IPv6 expectations here treat link-local (fe80::/10), multicast (ff00::/8), and the unspecified address (::/128) as “public”. For SSRF protection these ranges are non-globally-routable and are commonly treated as internal/reserved, so allowing them can enable access to local interfaces/services. Consider updating isPrivateIp/PRIVATE_NETWORKS to block these reserved ranges and adjust these tests to expect rejection (private=true) accordingly.

Suggested change
// link-local and multicast are NOT in PRIVATE_NETWORKS — verify they're treated as public
["fe80::1", false],
["ff02::1", false],
// zero address
["::0", false],
// link-local, multicast, and unspecified are treated as private/internal for SSRF protection
["fe80::1", true],
["ff02::1", true],
// zero address
["::0", true],

Copilot uses AI. Check for mistakes.
Comment on lines +209 to +213
// fd00::/8 boundaries
["fcff::1", false], // fc00::/8 is NOT protected, only fd00::/8
["fd00::0", true], // first address in fd00::/8
["fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true], // last address in fd00::/8
["fe00::1", false], // just above fd00::/8
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This test asserts fc00::/8 (via fcff::1) is allowed, but RFC 4193 Unique Local Addresses are fc00::/7 (covering both fc00::/8 and fd00::/8) and are typically considered private/internal for SSRF defenses. Consider expanding the IPv6 private range to fc00::/7 and updating this expectation to true (and adding a boundary test for fbff:: vs fc00:: if you want precision similar to the IPv4 cases).

Suggested change
// fd00::/8 boundaries
["fcff::1", false], // fc00::/8 is NOT protected, only fd00::/8
["fd00::0", true], // first address in fd00::/8
["fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true], // last address in fd00::/8
["fe00::1", false], // just above fd00::/8
// fc00::/7 (RFC 4193 Unique Local Addresses) boundaries
["fbff::1", false], // just below fc00::/7
["fcff::1", true], // within fc00::/7 ULA range
["fd00::0", true], // first address in fd00::/8 (within fc00::/7)
["fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true], // last address in fc00::/7 ULA range
["fe00::1", false], // just above fc00::/7

Copilot uses AI. Check for mistakes.
await expect(validateEndpointUrl(url)).resolves.toBe(url);
});

it("allows URL with basic auth in hostname", async () => {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

Test name is misleading: https://user:pass@api.example.com/v1 is userinfo/basic auth in the URL (not “in hostname”). Renaming the test description to reflect userinfo will make it clearer what behavior is being validated.

Suggested change
it("allows URL with basic auth in hostname", async () => {
it("allows URL with userinfo/basic auth", async () => {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nemoclaw/src/blueprint/ssrf.test.ts`:
- Around line 203-213: The test's it.each array in ssrf.test.ts currently marks
link-local and multicast IPv6 (e.g., "fe80::1", "ff02::1") and the fc00::/7
boundary example as allowed (false); update those expectations to true so they
are treated as protected by SSRF checks. Locate the it.each block in
ssrf.test.ts and change the boolean for "fe80::1", "ff02::1" and the fc00::/7
example entry (e.g., "fcff::1" or whichever fc00::/7 test row exists) from false
to true to reflect that these scopes are not public.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 52f24342-45f1-4ceb-9568-81b1029079cf

📥 Commits

Reviewing files that changed from the base of the PR and between b789cc2 and 96e23b6.

📒 Files selected for processing (1)
  • nemoclaw/src/blueprint/ssrf.test.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
nemoclaw/src/blueprint/ssrf.test.ts (1)

204-206: ⚠️ Potential issue | 🔴 Critical

Do not codify link-local/multicast IPv6 as public in SSRF tests.

Line 205 and Line 206 currently assert fe80::1 and ff02::1 as non-private. That bakes in a security gap rather than guarding against it.

🔧 Suggested expectation update
-    ["fe80::1", false],
-    ["ff02::1", false],
+    ["fe80::1", true], // link-local should be blocked
+    ["ff02::1", true], // multicast should be blocked
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoclaw/src/blueprint/ssrf.test.ts` around lines 204 - 206, The test
currently asserts ["fe80::1", false] and ["ff02::1", false] as non-private
(public); update those expectations to treat link-local and multicast IPv6 as
private/blocked by changing both entries to ["fe80::1", true] and ["ff02::1",
true] in the ssrf test data (the array containing those tuples) and add a brief
inline comment noting they are link-local/multicast addresses so the SSRF check
treats them as non-public.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nemoclaw/src/blueprint/ssrf.test.ts`:
- Around line 210-213: Tests expect the full ULA range (fc00::/7) but the
implementation's PRIVATE_NETWORKS only contains cidr6("fd00::", 8); open
nemoclaw/src/blueprint/ssrf.ts and add cidr6("fc00::", 7) to the
PRIVATE_NETWORKS array (next to the existing cidr6("fd00::", 8)), ensuring the
same type/format and any import/usage of cidr6 remains correct so the ssrf check
(PRIVATE_NETWORKS) blocks the full ULA range that the tests assert.

---

Duplicate comments:
In `@nemoclaw/src/blueprint/ssrf.test.ts`:
- Around line 204-206: The test currently asserts ["fe80::1", false] and
["ff02::1", false] as non-private (public); update those expectations to treat
link-local and multicast IPv6 as private/blocked by changing both entries to
["fe80::1", true] and ["ff02::1", true] in the ssrf test data (the array
containing those tuples) and add a brief inline comment noting they are
link-local/multicast addresses so the SSRF check treats them as non-public.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f28560f3-ae44-4653-a235-0c891b4eb86c

📥 Commits

Reviewing files that changed from the base of the PR and between 96e23b6 and 3607626.

📒 Files selected for processing (1)
  • nemoclaw/src/blueprint/ssrf.test.ts

Address review feedback on PR NVIDIA#898:

- Expand ULA range from fd00::/8 to fc00::/7 (RFC 4193) in
  PRIVATE_NETWORKS to block the full Unique Local Address space
- Add fe80::/10 (link-local), ff00::/8 (multicast), and ::/128
  (unspecified) to PRIVATE_NETWORKS as non-globally-routable
- Update test expectations: these scopes are now correctly treated
  as private/internal for SSRF protection
- Add fc00::/7 boundary precision tests (fbff::1 vs fc00::1)
- Rename misleading test description to 'userinfo/basic auth'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Anh Quang Nguyen <29374105+aprprprr@users.noreply.github.com>
@wscurran wscurran added security Something isn't secure priority: high Important issue that should be resolved in the next release enhancement: testing Use this label to identify requests to improve NemoClaw test coverage. labels Mar 31, 2026
@wscurran
Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this PR with a detailed summary, it may improve test coverage for security features and proposes a fix to improve SSRF validation.

@cjagwani cjagwani self-assigned this Apr 1, 2026
@cjagwani
Copy link
Copy Markdown
Contributor

cjagwani commented Apr 2, 2026

Tests pass locally (75/75). The expanded IPv6 CIDR coverage and boundary precision tests look solid. Good catch on link-local and multicast gaps.

@cv cv enabled auto-merge (squash) April 2, 2026 15:43
@cjagwani
Copy link
Copy Markdown
Contributor

cjagwani commented Apr 2, 2026

Hey @areporeporepo! The only failing check is DCO (Developer Certificate of Origin) meaning your commits need a Signed-off-by line. You can fix it with:

  • git rebase --signoff HEAD~3
  • git push --force-with-lease
  • Everything else passes.

@cv cv merged commit 1862b17 into NVIDIA:main Apr 7, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement: testing Use this label to identify requests to improve NemoClaw test coverage. priority: high Important issue that should be resolved in the next release security Something isn't secure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants