lightweight-server/agents/codex/done/2026-10-23-lightweight-security.md
ochenstarik-ui 9f06756cef
Some checks failed
Bash syntax / syntax (push) Failing after 11s
Bash syntax / syntax (pull_request) Failing after 2s
fix: close installer exposure and pin verified payloads
2026-10-23 12:33:19 +07:00

103 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Lightweight-server: S1–S10, 23.10.2026
Технический результат: **fixed**, S8 — **no_change** (защита уже была в исходном main). Релизная приёмка и merge остаются отдельным gate; автор ветку не вливает. База: `ef83559f7de19fd8649c78b22b05df397650cf46`. Ветка: `codex/lightweight-security-20261023`.
Задание: `agents/codex/inbox/2026-10-23-lightweight-security.md`. Изолированный worktree существующего репозитория, без clone. На хосте и реальных RU/DE/FI/DK серверах установщики и root-команды не запускались. Создана собственная одноразовая KVM Ubuntu 24.04; после проверок остановлена, её диски, seed и временные SSH-ключи удалены.
## Граница исправления
Установщики сами создавали публичные правила и wildcard-слушатели. Исправлена вся цепочка этапов 2/3/6, включая сохранённые управляемые правила и первый запуск upstream-установщика. Частичная правка одного списка портов оставляла бы прежние разрешения и другие этапы, открывающие их снова.
| Пункт | Изменение и подтверждение |
| --- | --- |
| S1 | По умолчанию только текущий/будущий SSH и TCP 443. Нет автоматического 80/2096/40000/63636. Старые управляемые правила требуют явного сохранения; при отказе удаляются реальные UFW-правила IPv4/IPv6 и лишь затем обновляется файл состояния. Проверены реальные правила в VM. |
| S2 | `webListen=127.0.0.1`, адрес проверяется через `ss`. Перед первым upstream start/restart временная обёртка systemctl применяет loopback к панели и отключает подписки; ошибка не допускает старт. Итоговая конфигурация проверяется до снятия защиты и после запуска. |
| S3 | Режимы панели и подписок независимы. Подписки: loopback / выключены / явно публичные со случайным путём. Прежний валидный приватный путь сохраняется; достаточно длинный секретный публичный путь сохраняется по умолчанию. Ротация запрашивает подтверждение и предупреждает о поломке старых URL. Путь в журнал не выводится. |
| S4 | Закреплены 3x-ui v3.10.2, Xray-install `05aa47266c94e6fb5ca48cad96afdb4becb5891f`, Xray-core v26.10.21 и SHA-256 всех используемых исполняемых payload amd64/arm64. Локальный helper проверяет их перед запуском; неизвестные транзитивные загрузки 3x-ui блокируются. GitHub либо HTTPS Forgejo mirror, без fallback. В README загрузка самого проекта по полному проверенному SHA и доверенной сумме, без mutable main. |
| S5 | После подтверждённого оператором нового входа удаляются правила 22/tcp и запись в управляемом списке. Если UFW всё ещё разрешает IPv4/IPv6 22 либо статус не проверен, скрипт не объявляет успех и сохраняет запись. Без подтверждения, при SSH=22 или MANAGE_UFW=no очистка не выполняется. |
| S6 | Только PAM open_session. Отправка через фиксированный `/usr/bin/setsid`, отдельный процесс, stdin/stdout/stderr закрыты. Производственные пути конфигурации и журнала фиксированы, без тестовых environment overrides. Тест: close не отправляет, open запускает одну отправку, возврат <500ms при curl, задержанном на 1s. |
| S7 | WARP — отдельный opt-in, по умолчанию выключен. |
| S8 | Уже присутствовали `--proto '=https' --tlsv1.2`; исходный файл не изменён, регрессия закреплена новым тестом. |
| S9 | Кавычки и обратные слэши экранируются в curl config, управляющие символы отклоняются до вызова curl. Проверены newline-инъекция и большой URL с vertical-tab, без pipefail/grep-q обхода. Секретная ссылка не передаётся в argv curl. |
| S10 | Опциональный age encrypt streaming без временного plaintext-архива; SCP с BatchMode и строгим заранее проверенным known_hosts. Ошибка выгрузки — ошибка операции. Повтор initial проверяет checksum и повторяет выгрузку; несовместимый старый plaintext при выбранном age явно отклоняется без удаления снимка. В VM настоящий age/zstd/tar round-trip совпал с исходным fixture. |
Файлы: этапы `2`, `user-3`, `tg-4`, `vpn-5`, `panel-warp-6`, `backup-7`; новый `ochenstarik-server-supply-chain.sh` и справочный `...pins.txt`; README EN/RU, CI workflow и четыре новых теста. Исходные восемь тестов не изменены.
## Проверки по порядку
1. `find . -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n` — PASS.
2. Новые поведенческие проверки:
- `tests/test-firewall-panel-security.sh`: defaults, миграция старых UFW-правил, IPv6 residual error, sudo без SSH_CONNECTION, независимые режимы/сохранение путей, startup guard, ожидание слушателя, подтверждение удаления 22;
- `tests/test-download-security.sh`: escaping/контрольные символы, cache shim lifecycle и запрет неизвестного installer URL, реальные вызовы helper из этапов 5/6, отсутствие upstream main, отсутствие fallback при ошибке mirror;
- `tests/test-notification-backup-security.sh`: PAM open/close, detached delivery, строгие SSH options и ошибка offsite;
- `tests/test-backup-initial-retry.sh`: реальный early-return путь initial, ошибка SCP → повтор, неизменность snapshot/checksum, age policy с прежним plaintext.
3. `for test in tests/*.sh; do bash "$test"; done` — **12/12 PASS**; `git diff --check` — PASS; diff исходных `tests/` пуст.
4. Одноразовая Ubuntu 24.04 amd64, настоящий systemd/UFW/3x-ui: этап 2 (полная установка), миграция старых правил, этап 6 fresh install и повтор с публичными подписками — exit 0.
5. Тот же VM: helper `prepare-xray` → `install-xray` — exit 0, реальный Xray **26.10.21**; age-backup round-trip — PASS.
## Настоящие UFW и TCP-слушатели
Ubuntu image SHA-256: `04e8f2461a16a44e78a370284471f29616cdd9baf1893df25807b71f985b6c94`, проверен по официальному SHA256SUMS cloud-images. SSH в тесте оставлен на 22; WARP выключен.
После полного этапа 2 и после удаления прежних 80/2096/40000/63636:
```text
ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
22/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
443/tcp (v6) ALLOW IN Anywhere (v6)
```
После fresh этапа 6 с приватной панелью и приватными подписками UFW остаётся тем же. Полный `ss -ltn` (сокращены только столбцы очередей):
```text
Local Address:Port Peer Address:Port
127.0.0.1:2096 0.0.0.0:*
127.0.0.1:2053 0.0.0.0:*
0.0.0.0:22 0.0.0.0:*
127.0.0.54:53 0.0.0.0:*
127.0.0.1:62789 0.0.0.0:*
127.0.0.53%lo:53 0.0.0.0:*
127.0.0.1:11111 0.0.0.0:*
[::]:22 [::]:*
```
DNS/внутренние Xray-порты остаются локальными. Разрешение UFW 443 не означает, что скрипт создаёт на 443 отдельный HTTPS-сервис. Во время sampled fresh bootstrap наблюдались только `127.0.0.1:49587` (временный upstream порт), `127.0.0.1:2053`, `127.0.0.1:2096`; sampling дополняет pre-start enforcement, не доказывает отсутствие любого мгновенного слушателя сам по себе.
При явном публичном доступе к подпискам UFW дополнительно содержит:
```text
2096/tcp ALLOW IN Anywhere
2096/tcp (v6) ALLOW IN Anywhere (v6)
```
`ss -ltn`: панель **127.0.0.1:2053**, подписки **\*:2096**. DB: `webListen=127.0.0.1`, `subListen=''`, `subEnable=true`. Секретный путь не выгружался в журнал.
## Два аудитора и собственная проверка
Дешёвые исполнители: `lightweight_firewall_panel`, `lightweight_downloads_notifications_backup`. До патча независимый boundary investigator; после кандидата два свежих read-only Codex auditor: `lightweight_security_audit_one`, `lightweight_security_audit_two`. Один цикл аудита, без самооценки авторского кода как релизного approval.
Подтверждены и исправлены найденные аудиторами: сохранённые UFW entries, игнорирование ошибки удаления 22, IPv6 статус, потеря/ротация прежнего subscription path, initial/offsite retry и несовместимая age policy, неточное описание pin manifest. Root дополнительно воспроизвёл реальные ошибки: sudo очищает SSH_CONNECTION, повторный mkdir shim и readiness race после systemctl start. После исправления повторил focused, все repository checks и настоящий VM-прогон. Неподтверждённое замечание об обязательной root escalation через PAM аудитор отозвал; тестовые env overrides всё равно удалены из производства.
## Evidence и пределы
Сырые обезличенные результаты сохранены в managed Codex Security artifact storage, target `lightweight-server`, bundle `artifacts-827d7430ed6521a319c17103be0c53cc1d742842b3e097b3a5f63693cead15d9`, каталог `artifacts/lightweight-20261023/`:
| Файл | SHA-256 |
| --- | --- |
| final-checks.log | 57be6a409fbce5488611bbc853eaecaacecdde8fa7c40656b87fb2ec925970f2 |
| ubuntu24-step2.log | 6e37718bf82ef1bf061449dee8782d79855946dca2ba5776e6bc9bada3dbec37 |
| ubuntu24-migration.log | 0257a0c97a8dd592c3492d886ad6fb42af2fedff7139eb70ea16387a8b2e2bab |
| ubuntu24-private.log | 4de0099004bcb79822b5a915d1e5914ea3b447e8b41e67471c730f75f39553c8 |
| ubuntu24-public.log | f0d75dd045e89ca9f8066542e4806b20c10724165e07f56f659b35c83728274f |
| ubuntu24-age.log | 737740b39ceba68214737f21283fea1d37a5f448c5bb3addcb6eef1d39fbcc09 |
| ubuntu24-xray.log | 270af1adae421035c3d97d4b0779c2d2f922e72208988036a3e534cff27c2a14 |
Полная VPN-маршрутизация реального сервера, WARP opt-in, arm64 execution и реальный SCP на чужой хост не запускались; вне требуемого Ubuntu amd64 прогона и разрешений. Mirror проверен на fail-closed отказ; готовый публичный mirror релиз не опубликован, нужны файлы с указанными суммами. Блок README требует реально одобренные SHA+checksum и не выдумывает готовый release.
По последнему правилу владельца новый PR ждёт Grok на точном SHA либо Claude по прямому запросу владельца. Два технических Codex-аудита этот release gate не заменяют. CI удалённой ветки проверяется отдельно после публикации; этот отчёт фиксирует локальные/VM результаты.