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
+8 -9
View File
@@ -135,10 +135,10 @@ export function assertValidAdminUsername(name: string, value: string): string {
* создавалась: bcrypt отвечал ErrPasswordTooLong при старте админки, и оператор
* получал не отказ установки, а сервер без администратора.
*
* Запрет управляющих символов — требование транспорта. Значение уезжает в
* /etc/hy2xs/hy2xs.env, который systemd читает как EnvironmentFile; границей
* строки там является байт `\n` или `\r`, и пароль с ними не имеет
* представления, переживающего запись и чтение. См. lib/envFile.ts.
* Управляющие символы формат EnvironmentFile способен нести; их отвергает
* политика HY2XS, потому что невидимое значение нельзя надёжно повторить в
* однострочной форме входа. Транспортный домен (включая U+FEFF) проверяется
* отдельно в lib/envFile.ts.
*
* Набор символов сверх этого НЕ ограничивается: сервер его не ограничивает ни
* при установке, ни при смене пароля, и оркестратор не имеет права быть строже.
@@ -158,10 +158,9 @@ export function isValidAdminPassword(value: string): boolean {
let characters = 0;
for (const character of value) {
const code = character.codePointAt(0) ?? 0;
// Продуктовая политика: управляющие символы Unicode целиком (Cc — C0, DEL,
// C1) и U+FEFF. Формат их несёт; запрещает их HY2XS, потому что ни один из
// них невозможно ни увидеть в поле ввода, ни повторить при следующем входе.
if (code < 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0xfeff) {
// Продуктовая политика: управляющие символы Unicode целиком (Cc — C0,
// DEL, C1). U+FEFF уже отвергнут транспортным доменом выше.
if (code < 0x20 || (code >= 0x7f && code <= 0x9f)) {
return false;
}
characters += 1;
@@ -179,7 +178,7 @@ export function assertValidAdminPassword(name: string, value: string): string {
throw new Error(
`invalid ${name}: панель принимает пароль длиной от ${ADMIN_PASSWORD_MIN_LENGTH} ` +
`до ${ADMIN_PASSWORD_MAX_LENGTH} символов Unicode, не длиннее ${ADMIN_PASSWORD_MAX_BYTES} байт ` +
`в UTF-8 и без управляющих символов; получено ${[...value].length} символов ` +
`в UTF-8, без управляющих символов и U+FEFF; получено ${[...value].length} символов ` +
`и ${Buffer.byteLength(value, "utf8")} байт. ` +
`Набор символов не ограничен, пробелы по краям являются частью пароля. ` +
`Установка с другим значением завершилась бы успешно, а войти в панель было бы нельзя.`