FLPATH-4634 | fix(catalog): parse array indices in nested field paths - #34
Conversation
Assisted by: Cursor Signed-off-by: igavra <igavra@redhat.com>
PR Summary by QodoFix catalog nested field paths to support array indices
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1. Unbounded index allocation
|
| raw, exists := parent[name] | ||
| var slice []any | ||
| if !exists { | ||
| slice = make([]any, index+1) | ||
| } else { | ||
| var ok bool | ||
| slice, ok = raw.([]any) | ||
| if !ok { | ||
| return nil, fmt.Errorf("path segment %q is not an array", pathSoFar) | ||
| } | ||
| if index >= len(slice) { | ||
| grown := make([]any, index+1) | ||
| copy(grown, slice) | ||
| slice = grown | ||
| } |
There was a problem hiding this comment.
1. Unbounded index allocation 🐞 Bug ☼ Reliability
setNestedValue/ensureSliceElementMap allocate/grow slices to length index+1 from the parsed path without any maximum bound or overflow guard, so a large (but valid) index can cause OOM or a runtime panic (e.g., index==MaxInt makes index+1 overflow) during spec building. These functions are called with catalog field paths in spec_builder, so a misconfigured/malicious catalog field path can crash the service while merging defaults/user values.
Agent Prompt
### Issue description
Indexed path segments (e.g., `disks[123]`) directly drive slice allocation via `make([]any, index+1)` and growth to `index+1` in multiple places. With no cap and no overflow guard, large indices can exhaust memory or panic due to `index+1` overflow.
### Issue Context
These helpers are used when building resource specs from catalog field defaults and user values.
### Fix Focus Areas
- Add an explicit maximum allowed array index (and/or maximum slice length) and return a descriptive error if exceeded.
- Add an overflow guard before computing `index+1` (e.g., reject indices where `index+1` would overflow or exceed the max).
- Apply the same validation consistently for both intermediate segments (`ensureSliceElementMap`) and last-segment indexed sets (`setNestedValue`).
- Add unit tests that assert large indices are rejected without attempting allocation.
- internal/catalog/service/nested_map.go[21-44]
- internal/catalog/service/nested_map.go[93-110]
- internal/catalog/service/nested_map.go[181-199]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| elem, ok := slice[index].(map[string]any) | ||
| if !ok { | ||
| return nil, fmt.Errorf("path segment %q[%d] is not a map", pathSoFar, index) | ||
| } |
There was a problem hiding this comment.
2. Duplicated index in error 🐞 Bug ⚙ Maintainability
ensureSliceElementMap can produce misleading error text by appending "[index]" to pathSoFar even though pathSoFar already includes the indexed segment, resulting in messages like "storage.disks[0][0]". This makes troubleshooting malformed shapes harder and can confuse error matching.
Agent Prompt
### Issue description
An error path is formatted as `pathSoFar[index]`, but `pathSoFar` already includes the bracketed index from the original segment (e.g., `storage.disks[0]`). This duplicates the index in error messages.
### Issue Context
`pathSoFar` is built from the original `parts` strings (which still contain `[n]`).
### Fix Focus Areas
- Change the error format to avoid re-appending `[index]` (e.g., `fmt.Errorf("path segment %q is not a map", pathSoFar)`), or pass an unindexed path string into `ensureSliceElementMap`.
- internal/catalog/service/nested_map.go[55-61]
- internal/catalog/service/nested_map.go[206-210]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| } | ||
|
|
||
| // parsePathSegment parses "key" or "key[n]"; malformed brackets return an error. | ||
| func parsePathSegment(seg string) (pathSegment, error) { |
There was a problem hiding this comment.
can't we use a regexp for that?
var segRe = regexp.MustCompile(`^([^\[\]]+?)(?:\[(\d+)\])?$`)
func parsePathSegment(seg string) (pathSegment, error) {
if seg == "" {
return pathSegment{}, fmt.Errorf("path segment cannot be empty")
}
m := segRe.FindStringSubmatch(seg)
if m == nil {
return pathSegment{}, fmt.Errorf("invalid path segment %q: expected name or name[index]", seg)
}
if m[2] == "" {
return pathSegment{name: m[1]}, nil
}
idx, _ := strconv.Atoi(m[2])
return pathSegment{name: m[1], index: idx, hasIndex: true}, nil
}
Test plan:
Code generation succeeds
go build ./... passes
go test ./internal/catalog/service/ -ginkgo.focus='array index paths|FLPATH-4634' -v passed (10 Passed 0 failed)
make fmt vet lint clean (0 issues)