0.134.26

Fixed (MEDIUM-аудит: 8 находок группы 4.7 Updater/Sandbox, см. docs/audit/backend-2026-06.md §4.7)

  • M-U-1 updater.SaveCurrentBinaryAsBackup + PerformBinaryRollback — атомарная запись через temp-файл + os.Rename + fsync родительской директории. Ранее O_TRUNC сразу затирал существующий backup, а сбой посреди io.Copy оставлял усечённый файл. Новый atomicCopyFile переиспользуется в обеих функциях.
  • M-U-2 updater.WritePendingFlag + WriteFailedFlagos.WriteFile заменён на writeFileSync (атомарная запись + fsync) + fsyncDir (sync родительской директории). Без fsync флаг мог остаться в page-cache при kernel crash между записью и рестартом worker’а.
  • M-U-3 updater.setExecutable — добавлена проверка strings.HasSuffix(exe, ".old"). На Windows после selfupdate процесс работает в target.old; ранее мы случайно делали chmod устаревшего файла вместо нового.
  • M-U-4 updater.CompareVersions + IsValidVersion — добавлена normalizeVersion (auto-prefix v). Раньше не-prefixed версии тихо считались равными (semver.Compare возвращал 0). Теперь "0.40.0" корректно сравнивается с "v0.39.0".
  • M-U-5 updater.ParseManifestMinSupported теперь проверяется на v-префикс (как Latest). Ранее невалидный min_supported: "0.40.0" отключал upgrade-gate (semver.Compare возвращал 0, условие >0 ложно → апгрейд проходил на слишком старую версию).
  • M-U-6 updater.Verify — убран hex-fallback. Теперь строго base64 (как пишет scripts/sign). Раньше fallback усложнял аудит и мог принять неожиданный формат от неподдерживаемого тулчейна.
  • M-U-7 (SKIPPED — operational) Ed25519 key rotation — PublicKeys []ed25519.PublicKey уже поддерживает несколько ключей (тест TestVerify_AcceptsAnyOfMultipleKeys). Добавление второго ключа — operational-процедура (genkey + deploy.sh), не код.
  • M-U-8 security.WrapCommand — при неизвестном backend теперь slog.Warn с указанием значения. Раньше молча возвращался nil, и команда запускалась без песочницы. Добавлен явный case SandboxNone: return nil (semantic clarity).
  • M-U-9 permissions.Warnings — добавлены 2 новых warning’а: (1) len(CustomAllowPatterns) > 0 — bypasses defaultDeny; (2) один и тот же regex в CustomAllow + CustomDeny — allow перебивает deny, последний становится мёртвым кодом.

Tests

  • Новые: internal/updater/medium_audit_test.goTestCompareVersions_NonVPrefix, TestParseManifest_RejectsBadMinSupported, TestParseManifest_AcceptsVPrefixedMinSupported, TestVerify_RejectsHexSignature, TestPendingFlagLifecycle_Atomic, TestPerformBinaryRollback_Atomic.
  • Новые: internal/permissions/validate_test.goTestWarnings_CustomAllowBypassesDefaultDeny, TestWarnings_CustomAllowAndDenyIntersect.
  • Новые: internal/security/sandbox_test.goTestWrapCommand_UnknownBackend.

Build / Tests

  • go test ./internal/... — все тесты проходят.
  • go build — OK.