From a5bd891d0a1b5bbc01513fca40aecb455b321148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 6 Aug 2026 17:30:22 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=B6=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BE=D0=BA=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=D0=B8=D1=82=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B7=D0=B3=D0=BE=D0=B2=D0=BE=D1=80=D0=B0,=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B8=D0=BC=D0=B5=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=83=D1=8E=20=D0=B2=20=D0=B7=D0=B0=D0=B3?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B2=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Надзиратель вскрыл дыру в самом опасном месте. Мой замок на личные данные смотрел только на имя файла, а имя подделывается первым. Запись разговора, положенная как obraztsy/Kakoe-to-imya.mp3 и вписанная в перечень слепков, проходила ВСЕ проверки насквозь. Замков стало три, и каждый ловит своё: - по имени, был — ловит небрежность, ничего не стоит, работает без машины; - по слепку, новый — слепок заготовки сверяется со слепками живых записей на машине; имя обмануть можно, слепок нельзя; - по содержимому, новый — запись разговора всегда WAV и всегда начинается с RIFF; любой RIFF среди заготовок обязан совпасть с одним из трёх известных слепков. Ловит подлог даже тогда, когда оригинал на машине уже съеден месячной уборкой. Три известных слепка прописаны в самом стороже, а не берутся из перечня: перечень подделывается тем же движением, что и подмена. Машину не грузим: сначала сравниваются размеры, и только совпавшие по размеру записи считаются слепком. Точная копия весит ровно столько же, значит замена полная. Сегодня совпадений по размеру ноль — вместо чтения 63 МБ звука машина читает одно оглавление. Решение владельца Р84. Показано красным двумя ножами, и ни одного чужого байта в дереве: - настоящая запись с машины, у которой длина звука ноль отсчётов, положена под безобидным именем — замки 2 и 3 назвали её поимённо, замок 1 остался зелёным; - подлог настоящего веса собран из наших же заготовок в отдельной папке на машине — замок 2 назвал оба файла. Подложенное убрано, возврат доказан cmp против содержимого коммита. Чего и новая защита не ловит: запись, ПЕРЕСЖАТУЮ в mp3. Записано в отчёте. Мерки: сторож 23 из 23, красных 0. Живая папка на машине не тронута. Co-Authored-By: Claude Opus 5 --- bots/lena-golos/README.md | 43 ++++- bots/lena-golos/proverka-zagotovok.sh | 111 ++++++++++++- .../otchyot-pomoshchnika-z-0-9-2026-08-06.md | 153 +++++++++++++++++- 3 files changed, 300 insertions(+), 7 deletions(-) diff --git a/bots/lena-golos/README.md b/bots/lena-golos/README.md index e14c5e69..b5dec6cb 100644 --- a/bots/lena-golos/README.md +++ b/bots/lena-golos/README.md @@ -521,7 +521,7 @@ cd ~/lena-inworld && venv/bin/python proba-mosta-bez-zvonka.py bash bots/lena-golos/proverka-zagotovok.sh ``` -21 проверка, и она **называет поимённо**, что именно пропало или испортилось. +23 проверки, и она **называет поимённо**, что именно пропало или испортилось. Смотрит на четыре вещи, каждая из которых по отдельности была бы слепой: 1. **хранилище** — все 43 файла на месте и сходятся со слепками. Не «файл @@ -550,6 +550,45 @@ LENA_BEZ_OPYTA=da bash bots/lena-golos/proverka-zagotovok.sh 🔴 **Сторожа надо кому-то запускать.** Сам он не ходит — ровно тот же открытый хвост, что и у сторожа уборки. +### 🔴🔴 Три замка на личные данные — и почему одного мало + +Заготовки лежат в той же папке, что и записи разговоров живых людей. Значит +самая страшная ошибка тут — не «заготовка пропала», а **«запись разговора +уехала в хранилище под видом заготовки»**. Замков три, и каждый ловит своё. + +| замок | как ловит | что ловит | чем обманывается | +|---|---|---|---| +| 1 — по имени | имя похоже на `1785699262.18.wav` | небрежность: принесли и не переименовали | переименованием за секунду | +| 2 — по слепку | слепок заготовки совпал со слепком живой записи на машине | умысел: запись переименовали в «образец голоса» и даже вписали в перечень слепков | если оригинал уже съеден месячной уборкой | +| 3 — по содержимому | файл начинается с `RIFF`, а слепок не из трёх известных | то же, что и 2, но **даже когда записи на машине уже нет** | только пересжатием звука в mp3 | + +**Почему замка 1 мало.** Он смотрит на имя, а имя — это то, что подделывается +первым. Файл, положенный как `obraztsy/Kakoe-to-imya.mp3` и вписанный в +перечень слепков, проходил **все** проверки: и «файлов столько же, сколько +слепков», и «все сходятся со слепками», и «лишних нет». Замерено 06.08.2026 — +это нашёл надзиратель, а не сторож. + +**Почему замок 2 не грузит машину.** Сначала сравниваются **размеры**, и только +совпавшие по размеру записи считаются слепком. Это не поблажка, а полная +замена: точная копия файла весит ровно столько же, сколько исходник. Сегодня +совпадений по размеру **ноль** — значит машина не читает ни одного байта звука +вместо 63 МБ. Машина делит железо с боевым рендером, решение владельца Р84. + +**На чём стоит замок 3.** Запись разговора — это **всегда** файл WAV: их пишет +`MixMonitor` телефонии, и такой файл начинается с четырёх букв `RIFF`. Замерено: +среди 43 заготовок файлов RIFF ровно **три**, и все три лежат верхним ярусом — +`chelovek.wav`, `chelovek-nomer.wav`, `chelovek-dorogo.wav`. Внутри восьми папок +подбора RIFF нет ни одного: их наполняют скрипты проб, а те пишут mp3. + +🔴 **Три известных слепка прописаны в самом стороже, а не берутся из +`zagotovki-slepki.sha256`** — и это главное. Перечень слепков подделывается тем +же движением, что и сама подмена: положил файл — вписал строку. Список внутри +сторожа этим движением не подделать. + +🪤 Поэтому **добавление честной новой заготовки WAV требует правки строки в +стороже руками.** Так и задумано: WAV — единственный вид файла здесь, способный +нести чужой голос. Для mp3 никакой такой возни нет. + ### Как вернуть заготовки на машину руками Обычно этого делать не надо — правило справляется само при загрузке. Но если @@ -735,7 +774,7 @@ $ sed 's/;.*//' bots/lena-golos/extensions-lena.conf | grep -c ZAPIS_RAZGOVOROV - + diff --git a/bots/lena-golos/proverka-zagotovok.sh b/bots/lena-golos/proverka-zagotovok.sh index ae14e7cd..b73d5c01 100644 --- a/bots/lena-golos/proverka-zagotovok.sh +++ b/bots/lena-golos/proverka-zagotovok.sh @@ -46,6 +46,10 @@ KLYUCH="${LENA_KLYUCH:-$HOME/.ssh/liderra_deploy}" # Пути на сервере. Меняются вместе с эталоном, не по отдельности. PAPKA_TAM="/tmp/lena-inworld" +# Где на машине лежат записи разговоров, с которыми сверяется замок 2. +# Переменной подменяется только ради проб самого сторожа — чтобы показать его +# красным, не трогая живую папку с чужими голосами. +PAPKA_ZAPISEY="${LENA_PAPKA_ZAPISEY:-$PAPKA_TAM}" POSTOYANNO_TAM="/home/ubuntu/lena-inworld/zagotovki" PRAVILO_TAM="/etc/tmpfiles.d/lena-zagotovki.conf" # 🔴 Имя нарочно длинное и явно временное — чтобы его нельзя было спутать ни с @@ -74,6 +78,43 @@ itog_i_vyyti() { # заготовках их быть не может НИКОГДА. OBRAZETS_ZAPISI='^(vh-)?[0-9]{9,}\.[0-9]+\.wav$' +# 🔴🔴 ТРИ ЗАМКА НА ЛИЧНЫЕ ДАННЫЕ, И КАЖДЫЙ ЛОВИТ СВОЁ. +# +# Замок 1 — ПО ИМЕНИ. Ловит небрежность: файл принесли и не переименовали. +# Дешёвый. Обманывается переименованием за одну секунду. +# Замок 2 — ПО СЛЕПКУ против живых записей на машине. Ловит умысел: +# запись переименовали в «образец голоса» и даже вписали в +# перечень слепков. Имя обмануть можно, слепок нельзя. +# Замок 3 — ПО СОДЕРЖИМОМУ. Ловит то, чего замок 2 уже не достанет: +# запись, которой на машине больше НЕТ — её съела месячная +# уборка, — но копия осела в заготовках. +# +# 🔴 Почему замок 1 не убран, хотя замки 2 и 3 сильнее. Он ничего не стоит и +# ловит частый случай — усталую руку, а не злой умысел. Замки 2 и 3 требуют +# живой машины и чтения байтов; замок 1 работает всегда. +# +# Основание замка 3. Запись разговора — это ВСЕГДА файл WAV: их пишет +# MixMonitor телефонии, и такой файл начинается с четырёх букв «RIFF». +# Замерено 06.08.2026: среди 43 заготовок файлов RIFF ровно ТРИ, и все три +# лежат верхним ярусом — chelovek.wav, chelovek-nomer.wav, chelovek-dorogo.wav. +# Внутри восьми папок подбора RIFF нет ни одного: их наполняют скрипты проб, +# а те пишут mp3 — все 39 файлов начинаются с байтов ff fb. +# +# 🔴 Поэтому правило замка 3 закрытое и НЕ зависит от имени файла: любой файл +# RIFF среди заготовок обязан совпасть слепком с одним из трёх известных. +# Переименованная запись — тоже RIFF, но слепок у неё другой, и она +# попадётся, даже если её вписали в перечень слепков и положили на машину. +# +# 🔴 Три слепка стоят ЗДЕСЬ, а не берутся из zagotovki-slepki.sha256, и это +# главное. Перечень слепков подделывается тем же движением, что и сама +# подмена: положил файл — вписал строку. Список, лежащий в стороже, +# подделать этим движением нельзя. Добавление честной новой заготовки WAV +# потребует правки этой строки руками — так и задумано: WAV здесь +# единственный вид файла, способный нести чужой голос. +IZVESTNYE_WAV="ad7e7c6b819e305fe4eb91241696a848fc4c2e0874816709ec47d2df69c4565e +70a352acf364bf6953f735a7c02633bdd9f49fe7e6cc06ac4396d0fd30aaa8af +71ad42e774ff6a49f348b3886d4d1ae922b5e38d94002bf919c8f9dedbb25c18" + echo "Сторож заготовок подбора голоса." echo @@ -125,11 +166,31 @@ fi # настоящего разговора. Если оказалась — это утечка, и круг остановлен. ZAPISI="$( cd "$ZAGOTOVKI" && find . -type f -printf '%f\n' | grep -E "$OBRAZETS_ZAPISI" )" if [ -z "$ZAPISI" ]; then - zelenyy "записей разговоров среди заготовок НЕТ — личные данные не утекли" + zelenyy "замок 1, по имени: файлов с именем записи разговора нет" else - krasnyy "🔴 СРЕДИ ЗАГОТОВОК ЗАПИСЬ РАЗГОВОРА — ЭТО ЛИЧНЫЕ ДАННЫЕ:" + krasnyy "🔴 замок 1: СРЕДИ ЗАГОТОВОК ЗАПИСЬ РАЗГОВОРА — ЭТО ЛИЧНЫЕ ДАННЫЕ:" echo "$ZAPISI" | sed 's/^/ | /' fi + +# 🔴🔴 ЗАМОК 3 — ПО СОДЕРЖИМОМУ. Имя не спрашиваем вовсе: читаем первые четыре +# байта. Запись разговора — всегда RIFF, и слепок у неё не совпадёт ни с +# одним из трёх известных. +CHUZHOY_WAV="" +while IFS= read -r F; do + [ -n "$F" ] || continue + [ "$(head -c 4 "$ZAGOTOVKI/$F" 2>/dev/null)" = "RIFF" ] || continue + H="$(sha256sum "$ZAGOTOVKI/$F" | cut -d' ' -f1)" + echo "$IZVESTNYE_WAV" | grep -qxF "$H" || CHUZHOY_WAV="$CHUZHOY_WAV$F слепок $H +" +done </dev/null | LC_ALL=C sort -u; echo '###ZAMERENO'")" + +if ! echo "$OTVET_RAZMEROV" | grep -qF '###ZAMERENO'; then + krasnyy "размеры записей на машине СПРОСИТЬ НЕ УДАЛОСЬ — подлог записью проверить нечем" +else + RAZMERY_ZAPISEY="$(echo "$OTVET_RAZMEROV" | grep -vF '###ZAMERENO' | grep -E '^[0-9]+$')" + SOVPALI_RAZMERY="$(comm -12 <(echo "$RAZMERY_ZAGOTOVOK") <(echo "$RAZMERY_ZAPISEY"))" + + if [ -z "$SOVPALI_RAZMERY" ]; then + zelenyy "замок 2, по слепку: ни одна заготовка не весит столько же, сколько запись — копией быть не может ни одна" + else + SKOLKO_RAZMEROV=$(echo "$SOVPALI_RAZMERY" | wc -l) + OTVET_SLEPKOV="$( echo "$SOVPALI_RAZMERY" \ + | ssh -o BatchMode=yes -o ConnectTimeout=30 -i "$KLYUCH" "$SERVER" \ + "cd '$PAPKA_ZAPISEY' 2>/dev/null && while IFS= read -r R; do [ -n \"\$R\" ] && find . -maxdepth 1 -type f ! -name 'chelovek*' -size \${R}c -print0; done | xargs -0 -r sha256sum | cut -d' ' -f1; echo '###ZAMERENO'" 2>/dev/null )" + + if ! echo "$OTVET_SLEPKOV" | grep -qF '###ZAMERENO'; then + krasnyy "слепки записей посчитать НЕ УДАЛОСЬ — подлог записью проверить нечем" + else + SLEPKI_ZAPISEY="$(echo "$OTVET_SLEPKOV" | grep -E '^[0-9a-f]{64}$' | LC_ALL=C sort -u)" + SLEPKI_ZAGOTOVOK="$(grep '^[0-9a-f]' "$SLEPKI" | cut -d' ' -f1 | LC_ALL=C sort -u)" + SOVPAVSHIE="$(comm -12 <(echo "$SLEPKI_ZAGOTOVOK") <(echo "$SLEPKI_ZAPISEY"))" + if [ -z "$SOVPAVSHIE" ]; then + zelenyy "замок 2, по слепку: $SKOLKO_RAZMEROV размер\ +(ов) совпал, слепки сверены — совпавших нет" + else + krasnyy "🔴 замок 2: ЗАГОТОВКА ПОБАЙТОВО РАВНА ЗАПИСИ РАЗГОВОРА. Это личные данные под чужим именем:" + for H in $SOVPAVSHIE; do + grep "^$H" "$SLEPKI" | sed 's/^[0-9a-f]* / | /' + done + fi + fi + fi +fi echo # ========================================== ЧАСТЬ 4 — ЖИВОЙ ОПЫТ =========== diff --git a/docs/superpowers/priyomka/stroyka-5/otchyot-pomoshchnika-z-0-9-2026-08-06.md b/docs/superpowers/priyomka/stroyka-5/otchyot-pomoshchnika-z-0-9-2026-08-06.md index 73ebc10b..39af7bce 100644 --- a/docs/superpowers/priyomka/stroyka-5/otchyot-pomoshchnika-z-0-9-2026-08-06.md +++ b/docs/superpowers/priyomka/stroyka-5/otchyot-pomoshchnika-z-0-9-2026-08-06.md @@ -132,7 +132,7 @@ vygruzka/*.md — пять промтов и баз знаний Лены, 107 | `bots/lena-golos/zagotovki/**` | 43 файла заготовок, 4 773 060 байт | | `bots/lena-golos/zagotovki-slepki.sha256` | слепок каждого из 43 файлов | | `bots/lena-golos/lena-zagotovki.conf` | эталон правила воссоздания, 12 строк `C` | -| `bots/lena-golos/proverka-zagotovok.sh` | сторож, 21 проверка | +| `bots/lena-golos/proverka-zagotovok.sh` | сторож, 23 проверки — три из них замки на личные данные | | `bots/lena-golos/README.md` | дописан раздел «Заготовки подбора голоса» | Перенос — одной свёрткой `tar` с **поимённым** перечнем из 12 имён верхнего @@ -236,7 +236,7 @@ vygruzka/*.md — пять промтов и баз знаний Лены, 107 Итоговое состояние сторожа: ```text -✅ ЦЕЛО: проверок 21, красных 0 +✅ ЦЕЛО: проверок 23, красных 0 ``` ### Мерка 2 — полный прогон @@ -371,8 +371,155 @@ systemd-tmpfiles-setup.service` → `ExecStart=systemd-tmpfiles --create --remov | правило доехало пустым, а systemd вернул 0 | 🔴 сторож врал зелёным, **починено** — см. ножи | | папки на машине нет, а сверка отвечает «сходится» | 🔴 сторож врал зелёным, **починено** — см. ножи | +## Добавка — слепота на личные данные, найденная надзирателем + +Надзиратель вскрыл дыру, которую я не увидел, и она в самом опасном месте. + +**Допущение, на котором стояла моя защита:** «запись разговора узнаётся по +имени». **Случай мимо:** файл, положенный под именем образца голоса и вписанный +в перечень слепков. Такой файл проходил **все** мои проверки — состав, слепки, +«лишних нет», ворота ПДн. Краснела только сверка с машиной, и то случайно: +потому что на машине файла не было. А мой же README велит новую заготовку +класть и на машину — то есть человек, добросовестно выполнивший мою инструкцию +с неверным файлом, получил бы **полностью зелёного сторожа** и личные данные в +коммите. + +Я с разбором согласен целиком. Замок был один, и он смотрел на то, что +подделывается первым, — на имя. + +### Что сделал: замков стало три + +| замок | как ловит | что ловит | +|---|---|---| +| 1 — по имени, был | имя похоже на `1785699262.18.wav` | небрежность | +| 2 — по слепку, новый | слепок заготовки совпал со слепком живой записи на машине | умысел | +| 3 — по содержимому, новый | файл начинается с `RIFF`, а слепок не из трёх известных | умысел **даже когда записи на машине уже нет** | + +Замок 1 не убран — как и просил надзиратель. Он ничего не стоит и работает +без машины. + +### По оговоркам надзирателя — по каждой отдельно + +**1. «Записи — вещь движущаяся, сторож не должен краснеть от смены их числа.»** +Учтено: сравниваются **множества слепков**, не числа. Число записей в замер не +входит вовсе — ни как порог, ни как ожидание. + +**2. «Не утяжели машину.»** Нашёл способ дешевле, чем предложенный. Сначала +сравниваются **размеры**, и только совпавшие по размеру записи считаются +слепком. Это не поблажка, а **полная замена**: точная копия весит ровно столько +же, сколько исходник, — значит запись, положенная без единого изменённого +байта, обязана совпасть по размеру. Замерено сегодня: разных размеров у +заготовок 38, у записей 44, **совпавших ноль**. То есть в обычном прогоне +машина не читает **ни одного байта** звука вместо 63 МБ. Размеры берутся из +`find -printf '%s'` — это чтение оглавления, не файлов. + +**3. «Поймать подлог файлом, которого на машине нет вовсе.»** Поймал, и без +выдуманной сложности — замком 3. Запись разговора всегда WAV, потому что её +пишет `MixMonitor`, а WAV начинается с `RIFF`. Замерено: среди 43 заготовок +файлов RIFF ровно **три**, все верхним ярусом; внутри восьми папок подбора — +**ноль**, там всё mp3 с байтами `ff fb`. Правило вышло закрытым и от имени не +зависит вовсе: любой RIFF среди заготовок обязан совпасть слепком с одним из +трёх известных. + +🔑 **И главное — где эти три слепка лежат.** В самом стороже, а не в +`zagotovki-slepki.sha256`. Перечень слепков подделывается тем же движением, что +и сама подмена: положил файл — вписал строку. Список внутри сторожа этим +движением не подделать. Цена — добавление честной новой заготовки WAV требует +правки строки руками; для mp3 никакой возни нет. Считаю цену правильной: WAV +здесь единственный вид файла, способный нести чужой голос. + +**4. «Проверку по имени не убирай.»** Не убрал. + +### Красным показано — двумя ножами, и ни одного чужого байта в дереве + +🔴 **С просьбой положить в дерево настоящую запись я не согласился в том виде, +в каком она была написана, и сделал безопаснее — не отказавшись от доказательства.** + +**Нож 6 — подлог надзирателя, дословно.** Взял с машины **настоящую** запись +`1785698951.10.wav` и положил её как `obraztsy/Proba-nadziratelya.mp3`, вписав +слепок в перечень. Почему это безопасно — замерено, а не заявлено: файл весит +**44 байта**, это голый заголовок WAV, и поле длины куска данных в нём равно +**нулю**: + +```text +52 49 46 46 24 00 00 00 57 41 56 45 66 6d 74 20 RIFF$...WAVEfmt +... +64 61 74 61 00 00 00 00 data.... + ^^^^^^^^ длина звука = 0 +``` + +Звука в файле нет ни одного отсчёта — голоса человека там физически нет. При +этом слепок у него **настоящий** и он **вправду** лежит среди записей на +машине, поэтому замок 2 обманут быть не может. Ответ сторожа: + +```text +✅ файлов столько же, сколько слепков: 44 +✅ все 44 заготовок сходятся со слепками +✅ лишних файлов в заготовках нет — ни одного без слепка +✅ замок 1, по имени: файлов с именем записи разговора нет ← имя безобидное +🔴 замок 3: файл RIFF, какого среди заготовок быть не должно: + | obraztsy/Proba-nadziratelya.mp3 слепок 4f8734c5…7041 +🔴 замок 2: ЗАГОТОВКА ПОБАЙТОВО РАВНА ЗАПИСИ РАЗГОВОРА: + | obraztsy/Proba-nadziratelya.mp3 +🔴 БЕДА: проверок 23, красных 3 +``` + +Замок 1 остался зелёным — значит поймали именно новые замки, а не старый. + +**Нож 7 — тот же подлог настоящего веса, без единого чужого байта.** Нож 6 +доказал ловлю, но на 44 байтах: отбор по размеру там почти не работал. Поэтому +второй опыт — на реальном весе. Подставными «записями» служили **наши +собственные** заготовки `Elena.mp3` весом 152 064 байта и `bylo.mp3` весом +92 928, положенные среди записей под именами записей — в **отдельной** папке на +машине, живую я не трогал. Для этого у сторожа появилась переменная +`LENA_PAPKA_ZAPISEY`, ради проб его самого. + +```text +🔴 замок 2: ЗАГОТОВКА ПОБАЙТОВО РАВНА ЗАПИСИ РАЗГОВОРА: + | obraztsy/Elena.mp3 + | brend/bylo.mp3 +``` + +Отбор по размеру отработал на настоящем весе и назвал **оба** файла. Подставная +папка снесена, снос проверен командой. + +### Возврат доказан, и по дороге я наступил на ту же грабку, что и надзиратель + +Подставной файл убран, строка из перечня вынута. Дальше вышло поучительно: + +```text +слепок перечня ДО: 33be0e5e…d4ca +слепок перечня ПОСЛЕ: 62674e39…8335 ← «РАЗОШЛОСЬ» +``` + +Тревога оказалась ложной — **той же самой, что подвела надзирателя**: разошлись +невидимые знаки конца строки. Настоящий замер: + +```text +git cat-file blob HEAD:…sha256 | sha256sum → 62674e391a70…8335 +sha256sum рабочей копии → 62674e391a70…8335 +cmp блоба и рабочей копии → ОДИН В ОДИН, 4173 байта +diff по строкам → различий нет +``` + +Строк в файле 47; копия с `CRLF` весила бы 4173 + 47 = 4220 байт. То есть +«ДО» я мерил копию с `CRLF`, а в коммите всегда лежала копия с `LF` — и +никакого расхождения содержимого не было ни секунды. Заготовок 43, строк +слепков 43. + +🔑 **Урок, который стоит вынести за пределы этого круга:** `sha256sum` рабочей +копии на Windows — **негодный** прибор для ответа «вернулось ли». Годный — +`cmp` против `git cat-file blob`, либо сверка со снятием `\r` с обеих сторон. +Мы с надзирателем попались на этом независимо друг от друга в один день. + ## Что осталось незакрытым +0. 🔴 **Чего не ловит и новая защита.** Замки 2 и 3 ловят запись, положенную + **байт в байт**. Запись, **пересжатую в mp3**, не поймает ни один из трёх: + имя другое, байты другие, `RIFF` пропал. Это уже не «переименовал», а + «нарочно перекодировал» — но честно: дыра осталась, и закрыть её дёшево + нечем. Ловится только человеком на разборе изменений. + 1. 🔴 **Сторожа никто не зовёт — ноль автоматических зовущих.** Ни расписания, ни крючка. Пока его не запускают, всё, что он умеет ловить, он будет ловить только тогда, когда кто-нибудь вспомнит. Ровно тот же открытый хвост, что у @@ -408,4 +555,4 @@ systemd-tmpfiles-setup.service` → `ExecStart=systemd-tmpfiles --create --remov - +