fix(tools): fail closed when the key scan cannot read the input
PR #66 review: json.Decoder.Token() fails on a number outside float64 range (1e999), which json.Unmarshal ignores in an unknown key, so a decoy key ended the scan and the #65 repro wrote to main. The decoder now uses UseNumber, and any scan error is a validation error. The duplicate check folds with strings.EqualFold (as encoding/json does) instead of ToLower, so long s and the Kelvin sign are caught in nested objects too. Refs #65 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-6
@@ -45,11 +45,16 @@ func parseArgs(raw json.RawMessage, dst any) error {
|
||||
// no top-level key may differ from a field of dst only in case.
|
||||
func rejectAmbiguousKeys(raw json.RawMessage, dst any) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(raw))
|
||||
// UseNumber: without it Token() fails on a number outside float64 range,
|
||||
// which json.Unmarshal ignores in an unknown key — a decoy that would end
|
||||
// the scan before the duplicate (PR #66 review).
|
||||
dec.UseNumber()
|
||||
if err := scanJSONValue(dec); err != nil {
|
||||
if errors.Is(err, gitea.ErrValidation) {
|
||||
return err
|
||||
}
|
||||
return nil // malformed JSON: let json.Unmarshal report it as before
|
||||
// Fail closed: input the scanner cannot read is input it cannot vouch for.
|
||||
return fmt.Errorf("arguments are not valid JSON: %v: %w", err, gitea.ErrValidation)
|
||||
}
|
||||
var top map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &top); err != nil {
|
||||
@@ -79,18 +84,21 @@ func scanJSONValue(dec *json.Decoder) error {
|
||||
}
|
||||
switch d {
|
||||
case '{':
|
||||
seen := map[string]string{}
|
||||
var seen []string
|
||||
for dec.More() {
|
||||
kt, err := dec.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k, _ := kt.(string)
|
||||
folded := strings.ToLower(k)
|
||||
if prev, dup := seen[folded]; dup {
|
||||
return fmt.Errorf("duplicate argument keys %q and %q (keys must be unique ignoring case): %w", prev, k, gitea.ErrValidation)
|
||||
// EqualFold, not ToLower: encoding/json folds the same way, so
|
||||
// long s (U+017F) matches s and the Kelvin sign matches k.
|
||||
for _, prev := range seen {
|
||||
if strings.EqualFold(prev, k) {
|
||||
return fmt.Errorf("duplicate argument keys %q and %q (keys must be unique ignoring case): %w", prev, k, gitea.ErrValidation)
|
||||
}
|
||||
}
|
||||
seen[folded] = k
|
||||
seen = append(seen, k)
|
||||
if err := scanJSONValue(dec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user