Skip to content

fix(fieldpath): do not recurse forever on a field named * - #1095

Open
arpitjain099 wants to merge 1 commit into
crossplane:mainfrom
arpitjain099:fix/fieldpath-literal-wildcard-key
Open

fix(fieldpath): do not recurse forever on a field named *#1095
arpitjain099 wants to merge 1 commit into
crossplane:mainfrom
arpitjain099:fix/fieldpath-literal-wildcard-key

Conversation

@arpitjain099

Copy link
Copy Markdown

Description of your changes

expandWildcards handles a wildcard segment over a map by substituting each key back into the segment list and re-expanding from the top:

expanded = append(append(expanded[:i], Field(k)), expanded[i+1:]...)
r, err := expandWildcards(data, expanded)

If one of those keys is literally *, the substituted segment is a wildcard again, so the call re-expands the identical input and never makes progress. The goroutine stack fills and Go reports fatal error: stack overflow, which is not recoverable, so a recover() further up the stack cannot contain it.

Reproduced against main before the fix, with ExpandWildcards("spec[*]") on {"spec":{"*":"star"}}:

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

A literal * field cannot be represented in Segments at all: Segments.String renders such a field as [*], and parsing that back yields a wildcard. So rather than trying to expand it, this returns an error for that key. Objects without a * key are unaffected, and the array branch is untouched.

Test case LiteralWildcardKey added to TestExpandWildcards. It overflows the stack on the unmodified tree and passes with the change; the rest of the package passes either way.

Fixes #

I have:

  • Read and followed Crossplane's contribution process.
  • Run ./nix.sh flake check to ensure this PR is ready for review. (ran go test ./pkg/fieldpath/..., go vet and gofmt locally; happy to run the full flake check if you want it)
  • Added or updated unit tests.
  • Linked a PR or a docs tracking issue to document this change. No user-facing docs change.
  • Added backport release-x.y labels to auto-backport this PR. Let me know if you want this backported.

expandWildcards substitutes each map key back into the segment list and
re-expands from the top. A key that is literally "*" is substituted in
as a wildcard segment again, so the same input expands over and over
until the goroutine stack is exhausted. That is a fatal error, not a
panic, so no recover() upstack can contain it.

Segments cannot represent a literal "*" field (Segments.String renders
one as [*], which parses back as a wildcard), so return an error for
that key instead.

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
@arpitjain099
arpitjain099 requested a review from a team as a code owner August 3, 2026 02:12
@arpitjain099
arpitjain099 requested a review from jbw976 August 3, 2026 02:12
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

expandWildcards now rejects literal * object keys during wildcard expansion. A test verifies the returned ambiguity error.

Changes

Wildcard ambiguity handling

Layer / File(s) Summary
Reject ambiguous wildcard keys
pkg/fieldpath/paved.go, pkg/fieldpath/paved_test.go
expandWildcards returns an error when an object contains a literal * key. The test verifies the error message.

Estimated code review effort: 2 (Simple) | ~5 minutes

Suggested reviewers: jbw976, adamwg, bobh66

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is 57 characters, stays under 72 characters, and clearly describes the recursion fix for literal wildcard fields.
Description check ✅ Passed The description clearly explains the stack overflow, the fix, the design rationale, and the added test.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Breaking Changes ✅ Passed The only production change adds a guard in unexported expandWildcards; no exported declarations or signatures change, and the old edge case crashed rather than returning usable behavior.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/fieldpath/paved.go`:
- Around line 219-220: The ambiguity error in the wildcard check within the
field-path parsing logic is not actionable. Update the errors.Errorf message in
the k == wildcard branch to explain that the conflicting field must be renamed
before retrying, while retaining the existing path and field details.
- Around line 216-221: In the field-path expansion logic surrounding the
mapOrArray iteration, scan for and reject a literal "*" key before entering the
for k := range mapOrArray loop, ensuring its ambiguity error takes precedence
regardless of map iteration order. Preserve the existing error details and add a
regression test covering the spec[*][*] path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eda39cd3-ee88-403d-9f41-659509e2ab43

📥 Commits

Reviewing files that changed from the base of the PR and between 5aeaaed and 98b7b0a.

📒 Files selected for processing (2)
  • pkg/fieldpath/paved.go
  • pkg/fieldpath/paved_test.go

Comment thread pkg/fieldpath/paved.go
Comment on lines +216 to +221
// A field literally named "*" would be substituted back
// in as a wildcard segment, so the expansion below would
// recurse on the same input forever.
if k == wildcard {
return nil, errors.Errorf("%q: object has a field named %q, which cannot be distinguished from a wildcard", segments[:i], wildcard)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Check for the literal * before expanding other keys.

Go map iteration order is not defined. If the path contains a later wildcard, another key can be expanded first and return a different error before this code reaches the literal * key. For example, spec[*][*] can report unexpected wildcard usage instead of the ambiguity error.

Move the literal-key check before the for k := range mapOrArray loop and add a regression case for this path.

Proposed fix
 case map[string]any:
+	if _, ok := mapOrArray[wildcard]; ok {
+		return nil, errors.Errorf("%q: object has a field named %q, which cannot be distinguished from a wildcard", segments[:i], wildcard)
+	}
 	for k := range mapOrArray {
-		if k == wildcard {
-			return nil, errors.Errorf("%q: object has a field named %q, which cannot be distinguished from a wildcard", segments[:i], wildcard)
-		}
-
 		expanded := make(Segments, len(segments))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// A field literally named "*" would be substituted back
// in as a wildcard segment, so the expansion below would
// recurse on the same input forever.
if k == wildcard {
return nil, errors.Errorf("%q: object has a field named %q, which cannot be distinguished from a wildcard", segments[:i], wildcard)
}
case map[string]any:
if _, ok := mapOrArray[wildcard]; ok {
return nil, errors.Errorf("%q: object has a field named %q, which cannot be distinguished from a wildcard", segments[:i], wildcard)
}
for k := range mapOrArray {
expanded := make(Segments, len(segments))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/fieldpath/paved.go` around lines 216 - 221, In the field-path expansion
logic surrounding the mapOrArray iteration, scan for and reject a literal "*"
key before entering the for k := range mapOrArray loop, ensuring its ambiguity
error takes precedence regardless of map iteration order. Preserve the existing
error details and add a regression test covering the spec[*][*] path.

Comment thread pkg/fieldpath/paved.go
Comment on lines +219 to +220
if k == wildcard {
return nil, errors.Errorf("%q: object has a field named %q, which cannot be distinguished from a wildcard", segments[:i], wildcard)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the ambiguity error actionable.

The message explains the conflict but does not tell the caller how to correct the input. State that the field must be renamed before retrying, because field-path syntax cannot represent a literal * separately from a wildcard.

As per path instructions, Go errors must provide user context and suggest next steps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/fieldpath/paved.go` around lines 219 - 220, The ambiguity error in the
wildcard check within the field-path parsing logic is not actionable. Update the
errors.Errorf message in the k == wildcard branch to explain that the
conflicting field must be renamed before retrying, while retaining the existing
path and field details.

Source: Path instructions

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant