Files
dualfroz d03cd5d09c fix(flag): honor full separator length when splitting map key=value pairs
splitKeyVal sliced the value from index+1, skipping only one byte of the
key/value separator. With a multi-character sep tag (e.g. sep:"=>") the
remaining separator bytes leaked into the value. Slice from index+len(sep)
so the whole separator is consumed.
2026-09-04 09:31:42 +02:00

1.7 KiB

fix(flag): honor full separator length when splitting map key=value pairs

Problem

Map flags support a configurable key/value separator via the sep tag. When the separator is more than one byte long, the tail of the separator leaks into the parsed value:

type T struct {
	Map map[string]string `cli:"m" sep:"=>"`
}
// parsing -mkey=>val yields Map["key"] == ">val", want "val"

Internally:

splitKeyVal("key=>val", "=>") // ("key", ">val"), want ("key", "val")
splitKeyVal("a::b", "::")     // ("a", ":b"),   want ("a", "b")

The default separator is = (single byte), so the common case is unaffected; the defect only appears for multi-character separators.

Root cause

flag.go, splitKeyVal:

index := strings.Index(s, sep)
if index == -1 {
	return s, "", nil
}
return s[:index], s[index+1:], nil

strings.Index returns the byte offset of the start of sep, but the value is sliced from index+1, which only skips a single byte of the separator instead of its whole length.

Fix

Skip the full separator length:

return s[:index], s[index+len(sep):], nil

Test

Added TestMapMultiCharSep in cli_test.go, covering both splitKeyVal directly and a map flag parsed through parseArgv with sep:"=>".

  • Gate: go test ./... -> ok (PASS, exit 0, all packages)
  • go build ./... -> exit 0
  • Counterfactual: reverting flag.go makes the test fail with splitKeyVal("key=>val", "=>") = ("key", ">val"), want ("key", "val").

Note: go vet ./... exits 1 both before and after this change, due to pre-existing example issues in example_test.go (ExampleNumOptionFunc_* refers to unknown identifier: NumOptionFunc), which are unrelated to this fix.