Этап 6: закалка — лимиты ADR-021, аудит модели угроз, сверка документов
Лимиты: все четыре правила ADR-021 — регистрация 5/час на IP, вход 10/10 мин на IP и ник, сообщения 30/мин, прочие изменяющие 60/мин; 429 с Retry-After; X-Real-IP читается только с loopback, иначе адрес соединения — иначе заголовок отменял бы лимит на IP; карты вёдер ограничены поколениями. Аудит нашёл то, что пропустили пять раундов ревью: ADR-056: nginx вёл access_log с IP и полными путями вопреки обещанию deploy.md. Ники и социальный граф ложились в /var/log/nginx рядом с чистым журналом bare. ADR-058: «выйти на других устройствах» не обрывал уже открытый SSE — отозванная сессия продолжала получать сообщения. ADR-059: промежуточный ключ комнаты был невосстановим. Участник, пропустивший офлайн два rekey подряд, навсегда не расшифровал бы сообщения среднего ключа — вопреки обещанию storage.md о повторной попытке после получения keyId. ADR-063: ACK уходил по одному на конверт, а не пачкой. Получатель в оживлённой комнате выедал общее ведро подтверждениями и упирался в 429 на всех изменяющих запросах, включая выход из комнаты: 116 отказов за прогон стало нулём. ADR-055, 057, 060, 061, 062: ключи вёдер и границы, +dirty у bare version, 403 unknown_device не хоронит сообщение, усечение имени в подсказке ввода, kdf как оракул после повышения цели KDF. Модель угроз пополнена тем, что действительно видит оператор: push-подписки лежат в базе открытым текстом, и вместе с VAPID-ключом с той же машины это произвольное уведомление на экране блокировки. README приведён к v1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
This commit is contained in:
+27
-4
@@ -81,6 +81,11 @@ func serve() error {
|
||||
}
|
||||
|
||||
h := api.New(cfg, st, static, os.Stdout)
|
||||
// Отправщики пушей дописывают начатое и пишут результат в базу, поэтому
|
||||
// остановить их надо раньше, чем закроется st. defer выстроен на это:
|
||||
// h.Close отложен позже st.Close и выполнится раньше него.
|
||||
defer h.Close()
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: h,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
@@ -93,7 +98,11 @@ func serve() error {
|
||||
|
||||
// Потоки событий не заканчиваются сами: без этого Shutdown ждал бы,
|
||||
// пока подключённые клиенты уйдут, до самого таймаута (ADR-004).
|
||||
srv.RegisterOnShutdown(h.Close)
|
||||
// Здесь только закрытие потоков: колбэк крутится в своей горутине,
|
||||
// и Shutdown его не дожидается — дождаться отправки пушей отсюда
|
||||
// нельзя. Их останавливает h.Close, когда Shutdown уже вернулся
|
||||
// и обработчики отработали.
|
||||
srv.RegisterOnShutdown(h.CloseStreams)
|
||||
|
||||
// Сначала bind, потом сообщение: строка в журнале означает, что порт занят
|
||||
// нами, а не то, что мы собирались его занять.
|
||||
@@ -154,15 +163,29 @@ func version() {
|
||||
fmt.Println(revision())
|
||||
}
|
||||
|
||||
// revision — ревизия сборки. У бинаря из изменённого рабочего дерева
|
||||
// к ней дописывается «+dirty»: сверка хеша со сборкой из тега — единственное
|
||||
// смягчение против подмены клиента (docs/threat-model.md), и чистый хеш
|
||||
// коммита у бинаря с чужими правками сводил бы её на нет (ADR-057).
|
||||
func revision() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return "unknown"
|
||||
}
|
||||
var vcs, modified string
|
||||
for _, s := range info.Settings {
|
||||
if s.Key == "vcs.revision" {
|
||||
return s.Value
|
||||
switch s.Key {
|
||||
case "vcs.revision":
|
||||
vcs = s.Value
|
||||
case "vcs.modified":
|
||||
modified = s.Value
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
if vcs == "" {
|
||||
return "unknown"
|
||||
}
|
||||
if modified == "true" {
|
||||
return vcs + "+dirty"
|
||||
}
|
||||
return vcs
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user