Skip to content

fix(fuzz): use OrderedMap for deterministic path segment iteration#6940

Open
jpirstin wants to merge 4 commits intoprojectdiscovery:devfrom
jpirstin:fix/fuzz-path-deterministic-order
Open

fix(fuzz): use OrderedMap for deterministic path segment iteration#6940
jpirstin wants to merge 4 commits intoprojectdiscovery:devfrom
jpirstin:fix/fuzz-path-deterministic-order

Conversation

@jpirstin
Copy link

@jpirstin jpirstin commented Feb 18, 2026

Proposed Changes

Fixes #6398

Path.Parse() used a plain Go map[string]interface{} for storing path segments, which has non-deterministic iteration order. This caused fuzzing templates to intermittently skip numeric path parts (e.g., /user/55/profile would sometimes not fuzz the 55 segment).

Root Cause

Go maps iterate in random order by design. When the path segments were stored in a plain map with string keys "1", "2", "3", the iteration order was non-deterministic, causing some segments to be skipped during fuzzing.

Fix

  1. Replace map[string]interface{} with mapsutil.OrderedMap in Path.Parse() to guarantee insertion-order iteration. This matches the pattern already used by the Cookie component.
  2. Fix Rebuild() to use KV.Get() accessor instead of directly accessing the .Map field, which is nil when using OrderedMap.

Test Results (run just now, rebased on latest dev)

$ go test ./pkg/fuzz/component/ -run "TestPath" -v
=== RUN   TestPathComponent_DeterministicOrder
--- PASS: TestPathComponent_DeterministicOrder (0.00s)
=== RUN   TestPathComponent_SQLInjection
    path_test.go:125: Original path: /user/55/profile
    path_test.go:129: Key: 1, Value: user
    path_test.go:129: Key: 2, Value: 55
    path_test.go:129: Key: 3, Value: profile
    path_test.go:148: Modified path: /user/55 OR True/profile
    path_test.go:156: Full URL: https://example.com/user/55 OR True/profile
--- PASS: TestPathComponent_SQLInjection (0.00s)
PASS
ok  github.com/projectdiscovery/nuclei/v3/pkg/fuzz/component 2.450s

The TestPathComponent_DeterministicOrder test runs 50 iterations to verify keys always come back in insertion order ["1", "2", "3"] with values ["user", "55", "profile"].

Files Changed

  • pkg/fuzz/component/path.go — Replace map with OrderedMap, fix Rebuild accessor
  • pkg/fuzz/component/path_test.go — Add deterministic order + SQLi injection tests

Checklist

  • PR created against the dev branch
  • Rebased on latest dev (Feb 18, 2026)
  • All tests pass locally (shown above)
  • Tests added that prove the fix is effective
  • Minimal diff — only touches the affected component
  • One issue, one PR — no other open submissions

/claim #6398

Summary by CodeRabbit

  • Bug Fixes

    • Path parsing now yields deterministic, stable segment ordering and preserves trailing slashes when rebuilding.
    • Replacements are applied only for valid non-empty string values, avoiding unintended segment changes.
  • Tests

    • Added deterministic-order and trailing-slash regression tests; improved assertion handling in existing tests.
  • Refactor

    • Internal segment handling updated for predictable, index-based ordering.

Path.Parse() used a plain Go map for storing path segments, which has
non-deterministic iteration order. This caused fuzzing templates to
intermittently skip numeric path parts (e.g., /user/55/profile would
sometimes not fuzz the '55' segment).

Fix: Replace map[string]interface{} with mapsutil.OrderedMap to guarantee
insertion-order iteration, matching the pattern already used by the Cookie
component.

Also fix Rebuild() to use the KV.Get() accessor instead of directly
accessing the .Map field, which is nil when using OrderedMap.

Fixes projectdiscovery#6398
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60d80b4 and 35a5a76.

📒 Files selected for processing (2)
  • pkg/fuzz/component/path.go
  • pkg/fuzz/component/path_test.go

Walkthrough

Replaces the plain map for path segments with an ordered map using 1-based numeric keys, updates parsing and Rebuild to use the ordered structure (with type-checked replacements and trailing-slash preservation), and adds tests verifying deterministic ordering and trailing-slash behavior plus test refactors to use require.

Changes

Cohort / File(s) Summary
Path component implementation
pkg/fuzz/component/path.go
Replaced plain map with mapsutil.NewOrderedMap, use 1-based numeric string keys for segments and dataformat.KVOrderedMap values; updated parsing population, Rebuild lookup/replace logic (type-assert string, preserve original if empty/non-string), re-append empty segment for trailing /, and adjusted imports (mapsutil, urlutil).
Tests — deterministic ordering & trailing slash
pkg/fuzz/component/path_test.go
Added TestPathComponent_DeterministicOrder (50 iterations asserting keys ["1","2","3"] and values ["user","55","profile"]) and TestPathComponent_TrailingSlash; refactored existing tests to use require assertions for error/condition checks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped along a numbered way,
Each segment kept, none led astray.
A slash remembered at the end,
Deterministic steps to mend.
The fuzzer dances—orderly play. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: replacing map with OrderedMap to ensure deterministic path segment iteration order.
Linked Issues check ✅ Passed The PR implementation directly addresses all coding requirements from issue #6398: replaces plain map with OrderedMap for deterministic iteration, fixes Rebuild() to use KV.Get() accessor, preserves trailing slashes, and adds regression tests with deterministic ordering assertions.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue: OrderedMap introduction for deterministic segment ordering, KV.Get() usage, trailing slash preservation, and regression tests. No unrelated modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
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.

🧹 Nitpick comments (2)
pkg/fuzz/component/path_test.go (1)

111-157: Mixed assertion style — consider aligning with require used in TestPathComponent_DeterministicOrder.

TestPathComponent_SQLInjection uses if err != nil { t.Fatal } / bare if newReq.Path != ... checks, while the new TestPathComponent_DeterministicOrder uses require.*. Aligning styles improves readability and prevents missing follow-on checks if a fatal error is encountered mid-test.

♻️ Proposed style unification
 func TestPathComponent_SQLInjection(t *testing.T) {
 	path := NewPath()
 	req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com/user/55/profile", nil)
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
 	found, err := path.Parse(req)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !found {
-		t.Fatal("expected path to be found")
-	}
+	require.NoError(t, err)
+	require.True(t, found)
 	// ...
 	err = path.Iterate(func(key string, value interface{}) error {
 		// ...
 		return nil
 	})
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)

 	newReq, err := path.Rebuild()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if newReq.Path != "/user/55 OR True/profile" {
-		t.Fatalf("expected path to be '/user/55 OR True/profile', got '%s'", newReq.Path)
-	}
+	require.NoError(t, err)
+	require.Equal(t, "/user/55 OR True/profile", newReq.Path)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/fuzz/component/path_test.go` around lines 111 - 157, The test
TestPathComponent_SQLInjection mixes t.Fatal/if checks with bare comparisons;
switch to the same require.* style used in TestPathComponent_DeterministicOrder
to improve consistency and fail-fast behavior: replace err checks (e.g., after
retryablehttp.NewRequest, path.Parse, path.Iterate, path.Rebuild) with
require.NoError calls and replace the final equality assertion comparing
newReq.Path with require.Equal (or require.Equalf) so all assertions use
testify/require and reference the TestPathComponent_SQLInjection function and
the variables req, path, newReq to locate and update the checks.
pkg/fuzz/component/path.go (1)

95-126: Trailing slash is silently dropped during Rebuild.

Parse skips all empty segments (including trailing ones from a path like /user/55/). Rebuild does the same, so a request for /user/55/ will be rebuilt as /user/55, dropping the trailing slash. This is likely pre-existing behavior carried forward from the old implementation, but it should be confirmed as intentional since it can alter the semantics of the request (some servers treat /resource/ and /resource differently).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/fuzz/component/path.go` around lines 95 - 126, Rebuild currently drops
trailing empty segments because the loop skips empty segments; after processing
segments in the Rebuild logic (the loop that iterates originalSplitted and uses
q.value.parsed.Get with keys like strconv.Itoa(segmentIndex)), detect if the
original path had a trailing slash (i.e., len(originalSplitted) > 1 &&
originalSplitted[len(originalSplitted)-1] == "") and if so append an empty
segment ("" ) to rebuiltSegments so joining will preserve the trailing slash;
keep the existing behavior of skipping intermediate empty segments but ensure
the leading empty segment is preserved as already handled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/fuzz/component/path_test.go`:
- Around line 111-157: The test TestPathComponent_SQLInjection mixes t.Fatal/if
checks with bare comparisons; switch to the same require.* style used in
TestPathComponent_DeterministicOrder to improve consistency and fail-fast
behavior: replace err checks (e.g., after retryablehttp.NewRequest, path.Parse,
path.Iterate, path.Rebuild) with require.NoError calls and replace the final
equality assertion comparing newReq.Path with require.Equal (or require.Equalf)
so all assertions use testify/require and reference the
TestPathComponent_SQLInjection function and the variables req, path, newReq to
locate and update the checks.

In `@pkg/fuzz/component/path.go`:
- Around line 95-126: Rebuild currently drops trailing empty segments because
the loop skips empty segments; after processing segments in the Rebuild logic
(the loop that iterates originalSplitted and uses q.value.parsed.Get with keys
like strconv.Itoa(segmentIndex)), detect if the original path had a trailing
slash (i.e., len(originalSplitted) > 1 &&
originalSplitted[len(originalSplitted)-1] == "") and if so append an empty
segment ("" ) to rebuiltSegments so joining will preserve the trailing slash;
keep the existing behavior of skipping intermediate empty segments but ensure
the leading empty segment is preserved as already handled.

@jpirstin
Copy link
Author

Friendly ping — any bandwidth to review this one? It fixes non-deterministic path segment ordering in the fuzzer's query parameter handler. Happy to address any feedback.

@jpirstin
Copy link
Author

CodeRabbit review is now complete (pass). Wondering if a maintainer could take a look when you get a chance — this is a targeted fix for non-deterministic OrderedMap iteration in path segment parsing with a regression test included. No changes to public API. Appreciate your time.

…lash in path Rebuild

- TestPathComponent_SQLInjection: replace t.Fatal/if checks with require.NoError,
  require.True, require.Equal to match the style used in TestPathComponent_DeterministicOrder
- path.go Rebuild: append empty trailing segment when original path ends with '/'
  so the rebuilt path also ends with '/' (servers may treat /resource/ vs /resource differently)
- Add TestPathComponent_TrailingSlash regression test
@jpirstin
Copy link
Author

Friendly ping — this PR has been open for 10 days and CodeRabbit review completed with no actionable comments. Happy to address any maintainer feedback or rebase if needed. Thanks for your time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Fuzzing templates skips numeric path parts

1 participant