Можно ли запретить автоматическое обновление Progressive Web App (PWA) на устройствах пользователей?

У меня PWA — приложение для заметок с открытым исходным кодом, и все данные заметок хранятся локально на устройстве пользователя. Можно ли настроить кэширование так, чтобы даже если я, как разработчик, изменю код на сервере, у пользователей по‑прежнему открывалась только закэшированная версия приложения и я не мог бы принудительно обновить PWA без их явного согласия? Сейчас я пытаюсь закешировать всё, но при открытии PWA отправляется GET-запрос на service.js, который возвращает 304 (Not Modified). Я могу переписать этот файл на сервере и тем самым насильно обновить приложение у пользователей (например, добавив скрипт, который отправит их заметки на мой сервер); чтобы обновление применилось, пользователю достаточно закрыть и снова открыть приложение. Как гарантировать, что разработчик не сможет тайно обновить приложение и получить доступ к локальным данным пользователя — какие есть способы или практики?
  • 13 февраля 2026 г. 10:21
Ответы на вопрос 2
Короткий ответ: полностью запретить браузеру автоматически получать и устанавливать новую версию PWA (если серверный код под контролем разработчика) нельзя. Браузер сам периодически проверяет script сервис‑воркера и может установить новую версию; если разработчик владеет сервером, он всегда может выдать другой файл и (при обычном поведении сервис‑воркера) новая версия возьмёт управление при следующем подходящем событии (закрытие/открытие, skipWaiting и т. п.). Поэтому единственный надёжный способ исключить «тихое» изменение — лишить разработчика возможности изменить код, либо сделать так, чтобы даже изменённый код не давал доступа к данным.

Развернуто — что важно знать и практические варианты.

1) Почему вы не можете полностью запретить обновления
- Браузер проверяет service-worker.js по URL области (scope). Даже если вы кэшируете всё, браузер делает conditional GET для скрипта и при изменении — будет этап установки новой версии. 304 говорит о том, что сервер ответил «не изменялось», но если вы поменяете файл на сервере, браузер получит новую версию.
- Механика lifecycle: при обнаружении новой версии браузер скачает её и запустит install. Новая версия активируется, когда текущие клиентские страницы закроются, или раньше — если код использует skipWaiting() и clients.claim(). Даже без skipWaiting, достаточно закрыть/открыть приложение — новая версия войдёт в силу.
- Поскольку вы (разработчик/владелец сервера) можете менять файлы на сервере, вы всегда можете доставить произвольный JS пользователю через обновление.

2) Что можно сделать технически, чтобы усложнить (но не исключить) «тихое» обновление
- Не вызывать skipWaiting() и не делать clients.claim(). Тогда новая версия не «перехватит» существующие открытые вкладки до тех пор, пока пользователь явно не перезапустит приложение. Это даёт пользователю время/видимость обновления, но не надежную защиту — достаточно закрыть и открыть приложение.
- Cache-first в fetch handler: отдавайте всё из кэша, не делая network-first. Это уменьшит сетевую активность app shell, но не помешает обновлению service-worker.js.
- Дайте service-worker.js HTTP-заголовок Cache-Control: max-age=... — браузеры всё равно будут проверять обновления периодически (обычно при навигации/загрузке в пределах scope), так что это не гарантирует отсутствие проверки.
- «Иммутабельный» URL для service-worker.js (с хешом в имени). Если версия файла не меняется, браузер не увидит новую версию. Но это работает лишь пока владелец сервера сознательно не поменяет URL/файл.

3) Единственные надёжные подходы против недоверенного разработчика
Если вы хотите гарантировать, что разработчик не сможет тайно изменить поведение приложения и получить доступ к локальным данным, нужно изменить модель доверия:

A) Клиентская шифровка (End-to-End Encryption)
- Шифруйте заметки на устройстве так, чтобы ключ никогда не передавался и не хранился на сервере. Ключ должен быть известен только пользователю (пароль → KDF → ключ).
- Реализуйте шифрование с Web Crypto API (AES‑GCM или похожее), храните только шифртексты в IndexedDB/localStorage.
- Минус: если злоумышленник (или разработчик) сможет исполнить произвольный JS в том же происхождении, он может перехватить пароль/ключ в момент ввода. Поэтому нужно сочетать с другими мерами (см. далее).

B) Доверенная поставка кода (пакетная/подписанная/локальная установка)
- Самый надёжный вариант — не полагаться на серверный код, который может менять владелец сервера:
  - Распространять приложение как нативное приложение (Electron, мобильный пакет) с кодом/подписью, тогда обновления контролирует магазин/пользователь.
  - Позволить пользователю установить из конкретного неизменного URL (например, конкретный коммит на GitHub raw урл) или скачать zip и запустить локально. Тогда девелопер не сможет подменить тот конкретный артефакт.
  - Использовать reproducible builds + подпись релизов и проверку хеша пользователем; это даёт пользователю возможность проверить целостность.

C) Аудит и reproducible builds
- Открыть процедуру сборки и предоставить инструкции/скрипты для пользователей, чтобы они могли собрать и проверить бинарь/скрипты сами. Это повышает доверие, но требует усилий со стороны пользователей.

D) Аппаратная/платформенная защита
- WebAuthn / ключи, TPM — но в браузере код все равно может читать ввод и запросы сети. Аппаратные ключи полезны скорее для аутентификации, чем для защиты локальных данных от исполняемого JS.

4) Практическая рекомендация для вашего случая (заметки на устройстве, OSS)
Если вы хотите, чтобы пользователи могли быть уверены, что вы (как разработчик) не сможете тайно получить их данные, сочетайте следующие меры:

- Сделайте клиент‑ское E2EE: генерируйте и храните ключ на клиенте, шифруйте заметки перед сохранением. Объясните пользователю, что ключ не покидает устройство.
- Проинформируйте пользователей и добавьте UX: показывайте версию приложения и предупреждение при обновлении (например, при обнаружении новой версии SW показывайте modal «Найдена новая версия. Требуется подтверждение для установки»). Уберите skipWaiting, чтобы пользователь видел, что появилось обновление, и мог согласиться.
- Предложите вариант self‑hosting / install from release: инструкции, как установить сервис‑воркер/контент из статического релиза (архива с зафиксированными файлами), или как запустить локально. Для продвинутых пользователей — ссылка на конкретный commit hash.
- Предоставьте reproducible builds и подписи релизов, инструкции по проверке хэшей.
- Ограничьте сбор данных и минимизируйте права доступа (CSP, отсутствие внешних аналитик/requests), чтобы уменьшить полезность потенциального «тихого» обновления.
- Документируйте threat model: честно объясните пользователю, что если он установил приложение с вашего сервера, вы как владелец сервера теоретически можете изменить код — и только E2EE + self-hosting/verified builds может это предотвратить.

5) Конкретный пример шифрования (очень кратко)
- При создании/открытии записной книги:
  - Спросите у пользователя пароль.
  - Получите ключ: key = PBKDF2(password, salt, iterations) или use scrypt; импортируйте в Web Crypto.
  - При сохранении: шифруйте JSON заметок AES‑GCM, сохраняйте ciphertext + nonce в IndexedDB.
  - При чтении: расшифровывайте на клиенте.
- Важно: не храните пароль/ключ на сервере и не передавайте его.

6) Что нельзя гарантировать в вебе
- Если код, который исполняется в origin, может быть изменён владельцем origin — он потенциально может логировать ввод пользователя, отправлять данные на сервер и т. п. Однозначная гарантия отсутствия изменений возможна только когда пользователю доступен способ проверить/подписать бинарный/скриптовый артефакт, который фактически исполняется (self-hosted, подписанные релизы, нативные пакеты).

Итог
- Технически запретить браузеру проверять/подхватывать обновления полностью нельзя, если вы контролируете сервер.  
- Надёжная защита приватных данных требует изменения модели доверия: либо пользователи контролируют источник кода (self‑hosting, проверяемые сборки), либо данные шифруются на клиенте с ключом, недоступным разработчику.  
- Практическая комбинация: клиентское E2EE + явное подтверждение обновлений + возможность установки из зафиксированного релиза/commit — даст пользователю реальную гарантию, что разработчик не сможет «тихо» получить заметки.

Если хотите, могу:
- показать пример клиентской схемы шифрования на Web Crypto,
- или предложить пример service-worker с «cache-only» стратегией и без skipWaiting + UX для ручного обновления.
Штатно - нет. 

PWA это в первую очередь Web приложение, и идеологически браузеры в принципе не дают никакого контроля пользователю над версиями (даже кеш там уже давно не принципиальный).

Пользователь может разработать расширение браузера, через который будет работать PWA (на android это будет проблематично, но вроде firefox можно сделать тем что поддерживает webview а через него уже будет работать PWA но надо изучать вопрос), и в этом расширении блокировать запросы и соответственно обновление (не подмена, поэтому нужно смотреть принципиальную возможность так сделать, а то после блокировки приложение может думать что произошла ошибка и соответственно реагировать). Как минимум с гарантией можно отслеживать изменения файлов и выдавать предупреждение - типа 'все, приложение обновилось, назад пути нет'.

Еще можно поставить между браузером и интернетом особо умную прокси, она и проконтролирует, и закеширует как надо, игнорируя заголовки, и подменит контент на необходимый...

И конечно модификация самого браузера, тут возможно любое поведение.

p.s. если вам нужно идеологически все же найти способ дать такую гарантию, то ваше приложение может работать через посредника, которому вы оба (разработчик приложения и пользователь) доверяете и который не будет делать гадости (например ему это не выгодно)... во времена хайпа децентрализованных сетей и блокчейна такие идеи витали, да и сейчас почему нет... можно представить сеть валидаторов (без блокчейна само собой), которые проверяют и может даже хостят и кешируют код (но не обязательно), а приложение на клиенте опрашивает их всех и загружает только то что они предоставляют.
Похожие вопросы