- в секции [telegram] заведён обязательный ключ enabled: умолчания у него нет, файл без него негоден; bot_token стал только ключом доступа и при enabled = false не читается вовсе, а пустой при enabled = true роняет старт - выключенный вход даёт подъём одним входом без единого обращения к Telegram и записью INFO вместо прежнего WARN: это выбор владельца, а не отклонение - отказ разбора файла настроек больше не пересказывает toml — её ParseError несёт в тексте разбираемое значение, и оборванная строка секретного ключа уносила его в журнал; теперь называются путь, строка, столбец и последний ключ
4.4 KiB
Разбор TOML: какое семейство отказов несёт значения из файла
Отвечает на вопрос, возникший по ходу задачи telegram-enabled-flag: можно ли
пересказывать отказ библиотеки разбора в журнал, если в файле настроек лежат
секреты. Наблюдение понадобилось потому, что ревью дизайна назвало этот путь
утечкой, а чинить его без разреза пришлось бы выбрасыванием всего текста отказа —
то есть платой разборчивостью на каждой опечатке.
Как снималось
Не замером, а чтением исходников зависимости, зафиксированной в go.mod:
github.com/BurntSushi/toml версии v1.5.0. Смотрел error.go, parse.go,
decode.go, meta.go, lex.go в кэше модулей. Дополнительно прогонял
toml.Decode на правдоподобных опечатках — в каталоге вне репозитория, чтобы не
править код проекта.
Что выяснилось
-
Значения из файла несёт ровно одно семейство отказов —
toml.ParseError. Его полеMessageсобирается из разбираемого куска:Invalid float value: %q(parse.go:341),invalid duration: %q,%v is out of range,Invalid integer %q. Туда же лексер отдаёт свои отказы черезpanicItemf(parse.go:134). -
Прочие отказы декодера значений не содержат вовсе. Их строит
md.e(decode.go:577) иmd.badtype— из имён ключей, имён типов (%TчерезfmtType) и длин. Обойдены все места:decode.go:282,288,297,329,348,385,388,399,428,437,467,487,518,552,561. -
LastKeyсекрета нести не может. Текущий ключ присваивается только послеitemKeyEnd, то есть после=(parse.go:200), а лексер ключа до=не доходит (lex.go:481-501). Посторонняя строка со значением ключом не станет. -
Поле
LineуParseErrorврёт, аPosition.Line— нет.panicErrиpanicItemfкладут в устаревшее полеLineзначениеit.pos.Len, то есть длину, а не номер строки (parse.go:97,106). Брать надоPosition.Line. -
ParseErrorвозвращается значением, не указателем (decode.go:564,parse.goцеликом), поэтомуerrors.Asберёт цельюtoml.ParseError, а не*toml.ParseError.Unwrapу типа нет. -
Ветка без последнего ключа достижима обычной опечаткой. Незакрытая скобка секции даёт
LastKey="":вход "[telegram\nenabled = true\n" → LastKey="" err=toml: line 2: expected '.' or ']' to end table name, but got '\n' instead
Что из этого следует для кода
Разрез по семейству отказа: ParseError пересобирается своими словами — путь,
строка, столбец, последний ключ, — а его Message не берётся; прочие отказы
проходят как есть. Так инвариант «Секрет не покидает конфиг» держится, а
несовпадение типов по-прежнему называет ключ и типы.
Наблюдение привязано к версии. Версия, переложившая значение в другое
семейство или сменившая возврат на указатель, вернёт утечку молча. Держат это
проверки поломанного файла настроек в internal/config/config_test.go; при
подъёме версии библиотеки их отказ читается как сигнал перечитать эту записку, а
не как случайный шум.