mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 14:30:13 +00:00
fix(server): fold keys the way encoding/json folds them (BUG-2803)
Codex round 17, first of five findings. The previous commit fixed the
ASCII half of key matching and left the Unicode half, which is this
bug's own pattern one more time.
encoding/json matches with Unicode SIMPLE FOLDING, not lower-casing.
U+017F LATIN SMALL LETTER LONG S folds to 's', so "ſchema" reaches the
`schema` struct field while strings.ToLower("ſchema") is unchanged and
missed the allowlist — a nested NUL under that spelling reached the
handler undetected.
Matching is now strings.EqualFold against each canonical key. The test
carries both a lower-case fold spelling and an upper-case one alongside
the ASCII cases, and keeps its control asserting an unlisted key stays
caller data in any casing.
The other four round-17 findings are recorded on the item rather than
patched here: they are genuine layer disagreements (duplicate keys
merging differently in a typed decode than in a map, a scan-failure
disposition on inputs the typed decode tolerates, and unknown-field
policy) whose fixes are design decisions rather than corrections, and
this seat is near its context bar. Each is written up with the
measurement it needs.
Claude-Session: https://claude.ai/code/session_011T365kP1N9V88y15HxL4YN
This commit is contained in:
@@ -1035,7 +1035,11 @@ func TestOAuthRegisterRefusesNULBody(t *testing.T) {
|
||||
// Measured before the fix: `fields` refused, `Fields` and `FIELDS` accepted.
|
||||
func TestBodyDecodesNULMatchesKeysLikeTheDecoder(t *testing.T) {
|
||||
inner := `{"k":"a` + escNULLiteral + `b"}`
|
||||
for _, key := range []string{"fields", "Fields", "FIELDS", "fIeLdS", "Schema", "TAGS"} {
|
||||
// The last two are Unicode simple-fold spellings: encoding/json matches
|
||||
// them to the same struct field, and lower-casing does NOT (codex round
|
||||
// 17). U+017F LONG S folds to 's'; U+212A KELVIN SIGN folds to 'k'.
|
||||
for _, key := range []string{"fields", "Fields", "FIELDS", "fIeLdS", "Schema", "TAGS",
|
||||
"\u017Fchema", "\u017FCHEMA"} {
|
||||
body := `{"title":"x","` + key + `":` + jsonEncode(t, inner) + `}`
|
||||
if !bodyDecodesNUL([]byte(body)) {
|
||||
t.Errorf("key %q reaches the same struct field as its lower-case spelling and must be "+
|
||||
|
||||
@@ -497,7 +497,18 @@ func isJSONEncodedFieldKey(k string) bool {
|
||||
if jsonEncodedFieldKeys[k] {
|
||||
return true
|
||||
}
|
||||
return jsonEncodedFieldKeys[strings.ToLower(k)]
|
||||
// EqualFold, not ToLower. encoding/json matches with Unicode SIMPLE
|
||||
// FOLDING, which is wider than lower-casing: U+017F LATIN SMALL LETTER
|
||||
// LONG S folds to 's', so "ſchema" reaches the `schema` field while
|
||||
// strings.ToLower("ſchema") is still "ſchema" and missed the allowlist
|
||||
// (codex round 17). Fixing the ASCII half and leaving the Unicode half
|
||||
// would have been this bug's own pattern one more time.
|
||||
for canonical := range jsonEncodedFieldKeys {
|
||||
if strings.EqualFold(k, canonical) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// valueDecodesNUL walks a decoded request body for a string that contains a
|
||||
|
||||
Reference in New Issue
Block a user