fix(utf8): закрыть lossy-границы конфигурации и API

This commit is contained in:
2026-09-07 17:25:27 +05:00
parent ab788725cf
commit af9f476658
38 changed files with 759 additions and 159 deletions
+19 -3
View File
@@ -597,10 +597,12 @@ production-профилем, а не ищет подстроки. Проверя
(`check_utf8ness_and_warn`), и отказ там — `-EINVAL`, то есть **незагруженный
файл окружения** и юнит, который не стартует. `unichar_is_valid` отвергает
суррогаты, `U+FDD0..U+FDEF` и все code points вида `*FFFE`/`*FFFF`, а сам
`utf8_is_valid` — встроенный NUL и невалидный UTF-8.
`utf8_is_valid` — встроенный NUL и невалидный UTF-8. Публичная документация
EnvironmentFile дополнительно запрещает U+FEFF. Реализация v257.13 случайно
пропускает его из-за маски; HY2XS следует документированному контракту.
`isEnvTransportable` в `lib/envFile.ts` повторяет ровно это множество и ничего
сверх него. Управляющие символы формат несёт — внутри двойных кавычек перевод
`isEnvTransportable` в `lib/envFile.ts` повторяет документированное множество.
Управляющие символы формат несёт — внутри двойных кавычек перевод
строки накапливается как обычный байт и переживает round-trip, — и запрещает их
контракт учётных данных, а не транспорт. Приписывать формату чужие запреты
нельзя: именно так проверка и пропустила noncharacters, о которых ничего не
@@ -610,6 +612,13 @@ production-профилем, а не ищет подстроки. Проверя
вправе их содержать, а `TextEncoder` молча заменит непарный суррогат на
`U+FFFD` — то есть без проверки в файл уехал бы **другой** секрет, а не отказ.
Сам файл читается только как байты и декодируется через
`TextDecoder("utf-8", { fatal: true, ignoreBOM: true })`. Обычный
`Bun.file(...).text()` запрещён на этой границе: он заменяет повреждённые байты
на U+FFFD. `ignoreBOM: true` сохраняет BOM как U+FEFF, чтобы тот не исчез до
транспортной проверки. Исходный текст целиком проверяется **до** разбора ключей:
запрещённый символ не может спрятаться в комментарии или неизвестной переменной.
### Непригодная конфигурация отвергается до первой мутации
`validateRuntimeEnvTransport` вызывается из `parseRuntimeEnv`, а не при записи
@@ -681,6 +690,13 @@ production-профилем, а не ищет подстроки. Проверя
`redact-config` и diagnostics-бандл используют **структурную** редакцию: YAML
разбирается и обходится как дерево.
Diagnostics не копирует env/YAML и не перенаправляет сырой journal/systemctl
сразу в staging. Сначала данные читаются или захватываются в память, проходят
редакцию и лишь затем записываются с режимом `0600`. Некорректный UTF-8 в
конфигурационном файле даёт безопасный маркер пропуска без исходных байтов.
Вывод каждой внешней команды ограничен 8 МиБ на поток и при усечении явно
помечается; архив создаётся сразу под `umask 077`.
Это не косметика. Построчное правило `auth:\s*(.*)` подставляло маркер в
заголовок mapping'а и оставляло нетронутым вложенный
`auth.http.url` с `access_token=<секрет>`, то есть бандл уносил machine token
+7 -3
View File
@@ -153,7 +153,7 @@ anycast. Отсутствие A-записи фатально при любом
| Переменная | Требование | Значение по умолчанию |
| --- | --- | --- |
| `HY2XS_ADMIN_USER` | 6-32 символа из набора `a-z A-Z 0-9 !@#$%^&*()_+,-./:;<=` | `hy2xsadmin` |
| `HY2XS_ADMIN_INITIAL_PASSWORD` | 6-64 символа Unicode **и** не более 72 байт в UTF-8; значение, загружаемое systemd из `EnvironmentFile=`; набор не ограничен, кроме `Cc` и `U+FEFF` | генерируется |
| `HY2XS_ADMIN_INITIAL_PASSWORD` | 6-64 символа Unicode **и** не более 72 байт в UTF-8; документированный транспортный домен systemd, включая запрет U+FEFF; набор не ограничен, кроме `Cc` | генерируется |
Значение вне контракта **роняет установку** с явным текстом, называющим границы
и набор. Так и должно быть: отказ, пришедший установщику, чинится одной строкой
@@ -193,7 +193,7 @@ HY2XS_ADMIN_INITIAL_PASSWORD="пароль с пробелом на конце "
Есть два ограничения набора символов, и они разного происхождения.
**Домен systemd.** Значение обязано быть загружаемым из `EnvironmentFile=`:
валидный UTF-8 из Unicode scalar values, без NUL, без суррогатов и без
валидный UTF-8 из Unicode scalar values, без NUL, U+FEFF, суррогатов и
noncharacters (`U+FDD0..U+FDEF` и все `*FFFE`/`*FFFF`). Это не наше правило —
systemd прогоняет значение через `utf8_is_valid` и отвечает `-EINVAL`, то есть
файл окружения **не загружается** и юнит не стартует. Оркестратор проверяет
@@ -201,9 +201,13 @@ systemd прогоняет значение через `utf8_is_valid` и отв
или obfs-пароль сломали бы загрузку юнита ровно так же.
**Политика HY2XS.** Сверх этого запрещены управляющие символы Unicode
(категория `Cc`) и `U+FEFF`. Формат их несёт, но ввести такой пароль в
(категория `Cc`). Формат их несёт, но ввести такой пароль в
однострочное поле формы входа всё равно нельзя.
Ограничение U+FEFF берётся из публичной документации systemd. В реализации
v257.13 он случайно проходит через `unichar_is_valid` из-за маски; HY2XS не
делает эту ошибку версии частью собственного контракта.
Отказ по любому из двух правил приходит **при разборе конфигурации**, то есть до
первой необратимой операции над хостом: `preflight-install` и `install` видят
его одинаково.