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
+7 -5
View File
@@ -82,8 +82,9 @@ export const ADMIN_PASSWORD_MAX_BYTES = 72;
* `/etc/hy2xs/hy2xs.env`, который systemd читает как `EnvironmentFile=`, и
* перед принятием пары прогоняет значение через `utf8_is_valid`. Отказ там —
* это не предупреждение, а незагруженный файл окружения и, следовательно,
* юнит, который не стартует. Отвергаются NUL, суррогаты, `U+FDD0..U+FDEF` и все
* code points вида `*FFFE`/`*FFFF`. Панель обязана знать это множество: иначе
* юнит, который не стартует. Публичный контракт отвергает NUL, U+FEFF,
* суррогаты, `U+FDD0..U+FDEF` и все code points вида `*FFFE`/`*FFFF`. Реализация
* v257.13 случайно пропускает U+FEFF; панель следует документации. Иначе
* она предложила бы оператору назначить пароль, после которого сервер
* перестанет подниматься.
*
@@ -93,7 +94,7 @@ export const ADMIN_PASSWORD_MAX_BYTES = 72;
* не отказ.
*
* Второе — продуктовая политика: управляющие символы Unicode целиком (Cc — C0,
* DEL, C1) и U+FEFF. Формат их несёт, запрещает их HY2XS: ни один из них
* DEL, C1). U+FEFF запрещён документированным транспортным доменом. Ни один из них
* невозможно ни увидеть в поле ввода, ни повторить при следующем входе, то есть
* они умеют ровно одно — запереть оператора снаружи.
*
@@ -109,6 +110,7 @@ export function isValidAdminPassword(value: string): boolean {
// Домен systemd: значение вне него не доедет до сервера вовсе.
if (
code === 0 ||
code === 0xfeff ||
(code >= 0xd800 && code <= 0xdfff) ||
(code >= 0xfdd0 && code <= 0xfdef) ||
(code & 0xfffe) === 0xfffe
@@ -116,8 +118,8 @@ export function isValidAdminPassword(value: string): boolean {
return false;
}
// Политика HY2XS: Cc и U+FEFF.
if (code < 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0xfeff) {
// Политика HY2XS: Cc. U+FEFF уже отвергнут транспортной веткой.
if (code < 0x20 || (code >= 0x7f && code <= 0x9f)) {
return false;
}
+2 -2
View File
@@ -27,7 +27,7 @@ export default {
credentials: {
usernameFormat: "Username: {min} to {max} characters from {charset}",
passwordFormat:
"Password: {min} to {max} characters and at most {maxBytes} UTF-8 bytes, no control characters",
"Password: {min} to {max} characters and at most {maxBytes} UTF-8 bytes, no control characters or U+FEFF",
},
dashboard: {
stale: "Dashboard data is stale. Retrying automatically...",
@@ -168,7 +168,7 @@ export default {
"“{field}”: {min} to {max} characters from {charset}. Spaces, non-latin letters and / : ; . are not allowed",
credential_format: "“{field}”: {min} to {max} characters from {charset}",
admin_password_format:
"“{field}”: {min} to {max} characters and at most {maxBytes} UTF-8 bytes, no control characters",
"“{field}”: {min} to {max} characters and at most {maxBytes} UTF-8 bytes, no control characters or U+FEFF",
rule_violated: "“{field}”: value is not acceptable",
validation_failed: "Validation failed",
body_invalid:
+2 -2
View File
@@ -36,7 +36,7 @@ export default {
// байта; сообщение «не длиннее {max} символов» отправило бы оператора
// сокращать пароль, отвергнутый не за это.
passwordFormat:
"Пароль: от {min} до {max} символов и не более {maxBytes} байт в UTF-8, без управляющих символов",
"Пароль: от {min} до {max} символов и не более {maxBytes} байт в UTF-8, без управляющих символов и U+FEFF",
},
dashboard: {
stale:
@@ -189,7 +189,7 @@ export default {
credential_format:
"«{field}»: от {min} до {max} символов из набора {charset}",
admin_password_format:
"«{field}»: от {min} до {max} символов и не более {maxBytes} байт в UTF-8, без управляющих символов",
"«{field}»: от {min} до {max} символов и не более {maxBytes} байт в UTF-8, без управляющих символов и U+FEFF",
rule_violated: "«{field}»: значение не подходит",
validation_failed: "Проверка данных не пройдена",
body_invalid: "Запрос не разобран: проверьте формат и типы полей",