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
+46 -1
View File
@@ -875,7 +875,7 @@ run_admin_login_acceptance() {
|| fail "acceptance: модуль формата env-файлов отсутствует"
code_has orchestrator/src/config/env.ts -F -- 'parseEnvFile(content)' \
|| fail "acceptance: разбор hy2xs.env снова расходится с systemd"
code_has orchestrator/src/config/env.ts -F -- 'renderEnvFile(entries)' \
code_has orchestrator/src/config/env.ts -F -- 'renderEnvFile(runtimeEnvEntries(config))' \
|| fail "acceptance: запись hy2xs.env снова идёт интерполяцией"
code_has orchestrator/src/steps/env.ts -F -- 'renderEnvFile(' \
|| fail "acceptance: bootstrap-admin.secret снова пишется интерполяцией"
@@ -908,6 +908,8 @@ run_admin_login_acceptance() {
|| fail "acceptance: $unicode_domain_owner не знает диапазон noncharacters U+FDD0..U+FDEF"
code_has "$unicode_domain_owner" -E -- '0[xX][fF][fF][fF][eE]' \
|| fail "acceptance: $unicode_domain_owner не знает noncharacters вида *FFFE/*FFFF"
code_has "$unicode_domain_owner" -E -- '0[xX][fF][eE][fF][fF]' \
|| fail "acceptance: $unicode_domain_owner не отвергает документированный U+FEFF"
done
# Одиночный суррогат в JS-строке TextEncoder молча заменяет на U+FFFD: без
# проверки это была бы не ошибка, а тихая подмена секрета.
@@ -918,6 +920,24 @@ run_admin_login_acceptance() {
|| fail "acceptance: $surrogate_owner не отвергает одиночные суррогаты"
done
log_step "Acceptance: text files use fatal UTF-8 decoding"
code_has orchestrator/src/lib/fs.ts -F -- '.bytes()' \
|| fail "acceptance: readText снова не читает исходные байты"
code_has orchestrator/src/lib/fs.ts -F -- 'fatal: true' \
|| fail "acceptance: readText снова допускает replacement decoding"
code_has orchestrator/src/lib/fs.ts -F -- 'ignoreBOM: true' \
|| fail "acceptance: reader снова съедает BOM до проверки U+FEFF"
"$BUN_BIN" -e '
const source = require("node:fs").readFileSync("orchestrator/src/lib/fs.ts", "utf8");
const start = source.indexOf("export async function readText");
const end = source.indexOf("\nexport async function writeText", start);
const body = source.slice(start, end);
if (body.includes(".text()")) throw new Error("readText использует replacement decoding");
' || fail "acceptance: строгий файловый reader заменён на Blob.text()"
! grep -R -F --include='*.ts' 'Bun.file(' orchestrator/tools \
| grep -F '.text()' >/dev/null \
|| fail "acceptance: build-time tool обходит строгий файловый reader"
# Порт автомата обязан повторять upstream буквально: в состоянии
# DOUBLE_QUOTE_VALUE_ESCAPE systemd пишет `c != '\n'`, а не проверку на любой
# перевод строки, - иначе `\<CR>` мы прочитали бы иначе, чем systemd.
@@ -935,11 +955,36 @@ run_admin_login_acceptance() {
const start = source.indexOf("export function parseRuntimeEnv");
if (start < 0) throw new Error("parseRuntimeEnv отсутствует");
const body = source.slice(start, source.indexOf("\nexport function validateRuntimeConfig"));
const rawValidation = body.indexOf("assertEnvTransportable");
const parsing = body.indexOf("parseEnv(content)");
if (rawValidation < 0 || parsing < 0 || rawValidation > parsing) {
throw new Error("исходный EnvironmentFile не проверяется до разбора");
}
if (!body.includes("validateRuntimeEnvTransport(config)")) {
throw new Error("parseRuntimeEnv не проверяет транспорт: отказ придёт уже после мутаций хоста");
}
' || fail "acceptance: проверка транспорта не входит в разбор конфигурации"
log_step "Acceptance: JSON is strict before encoding/json"
code_has apps/controller/json_body.go -F -- 'utf8.Valid(raw)' \
|| fail "acceptance: HTTP JSON снова допускает повреждённый UTF-8"
code_has apps/controller/json_body.go -F -- 'validateStrictJSONEncoding(raw)' \
|| fail "acceptance: strict JSON binder не проверяет Unicode escapes"
code_has apps/controller/validator.go -F -- 'strictBindJSON(c, &field)' \
|| fail "acceptance: DTO снова разбираются напрямую через Gin/encoding/json"
code_has apps/controller/hysteria2.go -F -- 'strictBindJSON(c, &req)' \
|| fail "acceptance: Hysteria auth обходит строгую JSON-границу"
code_has apps/controller/peer.go -F -- 'validateStrictJSONEncoding(content)' \
|| fail "acceptance: импорт пиров обходит строгую JSON-границу"
log_step "Acceptance: diagnostics never stages raw secrets"
! code_has orchestrator/src/commands/diagnostics.ts -E -- 'cp -a .*(hy2xs\.env|post-install\.env|config\.yaml)' \
|| fail "acceptance: diagnostics снова копирует секретный конфиг в staging до редакции"
code_has orchestrator/src/commands/diagnostics.ts -F -- 'prepareDiagnosticFile(source, sanitize)' \
|| fail "acceptance: diagnostics не проводит файл через строгую подготовку до записи"
! code_has orchestrator/src/commands/diagnostics.ts -F -- 'Bun.write' \
|| fail "acceptance: diagnostics снова пишет сырой промежуточный файл напрямую"
log_step "Acceptance: env files are read as data, never executed"
# Файл научился нести произвольные значения, и это сделало `source` опасным:
# для systemd `HY2XS_ADMIN_INITIAL_PASSWORD="$(...)"` - буквальное значение