Сканирование на вирусы для загружаемых файлов: варианты архитектуры для приложений
Объяснение сканирования на вирусы для загружаемых файлов в приложениях с большим количеством документов: карантин, очереди сканирования, контроль доступа, повторы и безопасные рабочие процессы выпуска.

Проблема простыми словами: небезопасные файлы в вашем приложении
Если ваше приложение позволяет людям загружать документы, вы принимаете файлы, которые не создавали вы. В продуктах с большим количеством документов (порталы для клиентов, HR‑системы, приложения для урегулирования претензий, онбординг поставщиков) загрузки часты, и пользователи часто передают файлы из почты, общих дисков или от третьих лиц. Это делает такие приложения удобной мишенью: одна успешная загрузка может распространиться через многочисленные скачивания.
Риски — это не только «вирус». Word или Excel могут содержать вредоносные макросы, PDF может быть специально подготовлен для эксплуатации уязвимости ридера, а «счёт‑фактура» может оказаться фишинговым документом, который заставит кого‑то позвонить на поддельный номер или ввести учётные данные. Некоторые файлы заражены скрытно: полезная нагрузка в ZIP, двойные расширения (report.pdf.exe) или встраивание удалённого контента, который «звонит домой» при открытии.
Нельзя полагаться на простой антивирус, установленный на одном сервере. Загрузки проходят через разные инстансы приложения, перемещаются между хранилищами или раздаются из объектного хранилища и CDN. Если какой‑то путь кода случайно откроет доступ к необработанной загрузке, пользователи смогут скачать её до завершения сканирования. Обновления, неверные настройки и «временный» доступ администратора тоже со временем обходят проверки.
Чёткая цель при сканировании на вирусы для загружаемых файлов проста: ни один не просканированный файл не должен быть доступен для скачивания или просмотра никому, кто не имеет явного права просматривать карантинный контент.
Определяйте, что значит «безопасно», как бизнес‑правило, а не как ощущение. Например:
- Должно пройти проверку на вредоносное ПО с актуальной базой сигнатур
- Должно соответствовать допустимым типам файлов и лимитам размера
- Должно храниться и раздаваться только из одобренных мест хранения
- Должна быть запись аудита: кто загрузил, когда и итоговый статус
- Должно быть заблокировано до окончательного решения: выпуск или отклонение
Если вы строите на платформе вроде AppMaster, рассматривайте «статус сканирования» как поле первого класса в вашей модели данных и требуйте проверки этого статуса при каждой попытке скачивания. Этот один шлюз предотвращает множество дорогих ошибок.
Что на самом деле значит карантин для загруженных документов
«Карантин» лучше воспринимать как состояние в системе, а не просто как папку в хранилище. Ключевая идея простая: файл существует, но никто не может его открыть или скачать, пока в приложении не появится чёткий и задокументированный результат сканирования. Это — суть сканирования на вирусы для загрузок.
Карантин обычно реализуют как небольшой жизненный цикл со явно выраженными статусами. Явное состояние сложнее случайно протечь через превью, прямую ссылку или задачу экспорта.
Практичный набор состояний файла выглядит так:
- received (загрузка завершена, ещё не просканирована)
- scanning (обработка воркером)
- clean (безопасно для выпуска)
- rejected (обнаружен вредоносный код или нарушение политики)
- failed (ошибка сканера, таймаут или повреждённый файл)
Карантин также требует правильных метаданных для возможности контроля доступа и последующего аудита. По минимуму храните: владельца (пользователь или организация), статус, оригинальное имя файла и тип, контрольную сумму (для дедупа и проверки целостности), место хранения и метки времени (загружен, сканирование начато, сканирование завершено). Многие команды также сохраняют версию сканера и детали вердикта.
Ретеншн — это политика, но она должна быть продуманной. Храните карантинные файлы только столько, сколько нужно для сканирования и отладки ошибок. Короткий срок хранения снижает риск и стоимость, но при этом нужно достаточно времени для расследований и ответов пользователям, которые спрашивают «куда делась моя загрузка?».
Наконец, решите, что делать с файлами, которые так и не прошли сканирование. Установите максимум времени на сканирование и отметку «истекает в». Когда дедлайн пройдёт, переводите файл в failed, блокируйте доступ и либо повторяйте попытки с ограничением, либо удаляйте файл и просите пользователя перезагрузить.
Шаблоны временного хранения, которые снижают риск
Именно временное хранилище — место, где чаще всего происходят проблемы с загрузками. Файл есть в системе, но ещё неизвестно, безопасен ли он, значит нужен участок, который легко заблокировать и сложно случайно открыть.
Локальный диск может работать для одного сервера, но это хрупко. При масштабировании на несколько серверов нужно шарить хранилище, копировать файлы и поддерживать согласованные права. Объектное хранилище (S3‑подобное или контейнер в облаке) часто безопаснее для приложений с большим количеством документов: правила доступа централизованы, логи понятнее.
Простой паттерн — разделять «карантин» и «чистое» хранилище. Это можно сделать двумя бакетами/контейнерами, что уменьшает шанс ошибки, либо строгими префиксами в одном бакете — это дешевле и проще в управлении.
Если используете префиксы, сделайте их недвусмысленными. Предпочтительна структура вроде quarantine/<tenant_id>/<upload_id> и clean/<tenant_id>/<document_id>, а не имена, введённые пользователем. Никогда не переиспользуйте один и тот же путь для разных состояний.
Соблюдайте правила:
- Не разрешайте публичные чтения для карантина, даже временно.
- Генерируйте имена объектов на сервере, а не берите от клиента.
- Разделяйте по tenant/аккаунту, чтобы уменьшить радиус поражения.
- Храните метаданные (владелец, статус, контрольная сумма) в базе данных, а не в имени файла.
Шифруйте данные в покое и строго контролируйте, кто может расшифровать. API загрузки должен иметь право писать в карантин, сканер — читать из карантина и записывать в «чистое», а публичное приложение — только читать из «чистого». Если облако поддерживает политики ключей, привязывайте права на расшифровку к минимально возможному набору ролей.
Крупные файлы требуют особого внимания. Для multipart‑загрузок не помечайте объект как «готовый», пока не будет зафиксирована последняя часть и ожидаемый размер/контрольная сумма. Частый безопасный подход: загружать части в карантин, затем копировать или повышать объект в чистое хранилище только после успешного сканирования.
Пример: в клиентском портале, собранном на AppMaster, каждую загрузку можно считать «в ожидании», сохранять в карантинном бакете и показывать кнопку скачивания только после смены статуса на «clean».
Варианты архитектуры: inline‑сканирование против фонового
При добавлении сканирования на вирусы для загрузок обычно выбирают между двумя потоками: сканирование inline (пользователь ждёт) или фоновое (приложение принимает загрузку, но блокирует доступ до очистки). Правильный выбор определяется не столько уровнем безопасности (оба варианта могут быть безопасны), сколько скоростью, надежностью и частотой загрузок.
Вариант 1: Inline‑сканирование (пользователь ждёт)
Inline‑сканирование означает, что запрос на загрузку не завершается, пока сканер не вернёт результат. Это кажется простым: одна операция — загрузка, сканирование, принятие или отклонение.
Inline подходит, когда файлы небольшие, загрузки редки и можно держать предсказуемое время ожидания. Например, внутренний инструмент, где пользователи загружают по несколько PDF в день, может терпеть паузу 3–10 секунд. Минус — медленное сканирование делает приложение медленным. Таймауты, повторы и мобильные сети ухудшают UX.
Вариант 2: Фоновое сканирование (асинхронно)
Асинхронное сканирование сначала сохраняет файл, помечает его как «в карантине» и ставит задачу в очередь сканирования. Пользователь получает быстрый ответ «загрузка получена», но не может скачать или просмотреть файл до очистки.
Этот подход лучше для больших объёмов, крупных файлов и пиковых нагрузок: он распределяет работу и сохраняет отзывчивость приложения. Можно масштабировать воркеры сканирования отдельно от веб‑/API‑серверов.
Практичный гибрид: выполнять быстрые проверки inline (разрешённые типы, лимиты размера, базовая валидация формата), а полный антивирусный скан делать в фоне. Это ловит очевидные проблемы сразу, не заставляя всех пользователей ждать.
Короткая подсказка для выбора:
- Маленькие файлы, низкая нагрузка, строгие требования «надо знать сейчас»: inline
- Большие файлы, много загрузок или непредсказуемое время сканирования: фон
- Жёсткие SLA по отклику при загрузке: фон + понятный UI статуса
- Смешанные нагрузки: гибрид (быстрые проверки сначала, полный скан асинхронно)
В AppMaster это часто сводится к синхронному API‑эндпоинту (inline) или Business Process, который ставит работу в очередь и обновляет статус файла по результатам.
Пошагово: построение асинхронной очереди сканирования
Асинхронное сканирование значит: вы принимаете загрузку, помещаете её в карантин и сканируете в фоне. Доступ даётся пользователю только после того, как сканер скажет, что всё чисто. Для документно‑нагруженных приложений это обычно наиболее практичная архитектура.
1) Определите сообщение очереди (делайте его компактным)
Думайте об очереди как о списке дел. Каждая загрузка создаёт одно сообщение, которое указывает на файл, а не содержит файл.
Простое сообщение обычно включает:
- ID файла (или ключ объекта) и tenant/project ID
- ID пользователя, который загрузил
- Временная метка загрузки и контрольная сумма (опционально, но полезно)
- Номер попытки (или отдельный счётчик ретраев)
Не кладите сырые байты в очередь. Большие полезные нагрузки ломают лимиты, стоят дороже и увеличивают зону риска.
2) Постройте поток воркера (забрать, просканировать, записать)
Воркер берёт сообщение, получает файл из карантинного хранилища, сканирует и записывает решение.
Чёткий поток действий:
- Получить файл по ID из карантинного хранилища (приватный бакет или приватный том)
- Запустить сканер (AV‑движок или сервис сканирования)
- Записать результат в базу: статус (clean, infected, error), имя/версию сканера и метки времени
- При clean: переместить файл в одобренное хранилище или переключить флаг доступа, чтобы он стал доступен для скачивания
- При infected: держать в карантине (или удалить) и уведомить ответственных
3) Сделайте процесс идемпотентным (безопасным при повторной обработке)
Воркеры падают, сообщения доставляются дважды, и ретраи происходят. Спроектируйте так, чтобы повторное сканирование одного и того же файла не навредило. Используйте единственный источник правды, например files.status, и разрешайте только валидные переходы, например: uploaded -> scanning -> clean/infected/error. Если воркер видит clean, он должен остановиться и подтвердить сообщение.
4) Контролируйте конкурентность (чтобы избежать «шторма» сканирований)
Ограничьте количество одновременных сканов на воркер и на tenant. Подумайте о отдельных очередях для больших файлов. Это не даст одному нагрузочному клиенту съесть всю мощность сканера.
5) Обрабатывайте ошибки ретраями и ведите аудит
Используйте повторы для временных ошибок (таймаут сканера, сетевые сбои) с небольшим лимитом попыток. После этого отправляйте сообщение в dead‑letter очередь для ручной проверки.
Ведите аудиторский след: кто загрузил документ, когда он попал в карантин, какой сканер был запущен, что он решил и кто одобрил или удалил файл. Этот лог так же важен, как само сканирование, особенно в порталах для клиентов и при соответствующих требованиях.
Контроль доступа: как действительно сделать карантин приватным
Карантин — это не просто статус в базе. Это обещание, что никто не сможет открыть файл, пока он не доказан как безопасный. Самое безопасное правило: никогда не отдавайте карантинные файлы по публичным URL, даже «временным».
Хороший поток скачивания — скучный и строгий. Приложение должно обрабатывать каждое скачивание как защищённое действие, а не как получение картинки.
- Запрос на скачивание
- Проверьте право пользователя на этот конкретный файл
- Проверьте статус файла (quarantined, clean, rejected)
- Отдайте файл только если статус clean
Если используете подписанные URL, придерживайтесь того же принципа: генерируйте их только после проверки прав и статуса, и делайте их короткоживущими. Короткий срок уменьшает вред, если ссылка просочилась в логах, скриншотах или была переслана.
Ролевая модель помогает избежать «особых случаев», которые превращаются в дырки. Типичные роли в документно‑нагруженных приложениях:
- Uploader: видит свои загрузки и их статус сканирования
- Reviewer: может просматривать чистые файлы и, в некоторых случаях, карантинные файлы только через защищённый инструмент проверки
- Admin: может расследовать, пересканировать и переопределять доступ при необходимости
- External user: имеет доступ только к документам, явно с которыми они поделены
Также защищайте от угадывания ID. Не публикуйте инкрементные идентификаторы вроде 12345. Используйте непрозрачные ID и всегда авторизуйте по пользователю и файлу (не только «входящий пользователь»). Даже при приватном бакете неаккуратный API‑эндпоинт может утечь содержимое карантина.
При внедрении сканирования на вирусы именно слой доступа даёт большинство реальных уязвимостей. На AppMaster эти проверки можно и нужно реализовать в API‑эндпоинтах и бизнес‑логике, чтобы карантин оставался приватным по умолчанию.
Выпуск, отклонение и повторы: обработка результатов сканирования
После завершения сканирования важно перевести файл в одно понятное состояние и сделать следующий шаг предсказуемым. Рассматривайте результат сканирования как ворота: ничего не становится доступным до тех пор, пока ворота не откроются.
Простой набор исходов покрывает большинство систем:
- Clean: выпустить файл из карантина и разрешить нормальный доступ.
- Infected: навсегда заблокировать доступ и запустить работу по обработке заражённых файлов.
- Unsupported: сканер не может оценить тип (например, зашифрован). Держать заблокированным.
- Scan error: временная ошибка (таймаут, сервис недоступен). Держать заблокированным.
Сообщения пользователю должны быть понятными и спокойными. Избегайте пугающих формулировок вроде «Ваш аккаунт взломан». Лучше: «Файл проверяется. Продолжайте работу.» Если файл заблокирован, укажите дальнейшие шаги: «Загрузите другой тип файла» или «Попробуйте позже». Для неподдерживаемых типов указывайте причину: «Зашифрованные архивы не подлежат сканированию».
Для заражённых файлов решите заранее: удалять или хранить. Удаление проще и снижает риск. Сохранение помогает для аудита, но только если файл хранится в изолированной зоне с жёстким доступом и коротким сроком хранения, и записано, кто имеет к нему доступ (в идеале — никто, кроме безопасности).
Ретраи полезны, но только для временных ошибок. Установите небольшую политику повторов, чтобы не накапливать бесконечную очередь:
- Повторять при таймаутах и недоступности сканера.
- Не повторять при «infected» или «unsupported».
- Ограничить попытки (например, 3) и затем пометить как failed.
- Использовать экспоненциальный бэкофф между попытками, чтобы избежать перегрузки.
Наконец, рассматривайте массовые ошибки как операционную проблему, а не как проблему пользователя. Если много файлов падают в «scan error», оповестите команду и приостановите релизы. В AppMaster эти состояния можно моделировать в базе и перенаправлять уведомления через встроенные модули, чтобы нужные люди быстро узнали о проблемах.
Пример сценария: клиентский портал с большим количеством документов
Клиентский портал позволяет клиентам загружать счета и контракты по проектам. Здесь сканирование при загрузках особенно важно, потому что пользователи затащат на портал всё, что у них на рабочем столе, включая файлы, пересланные другими.
Когда клиент загружает PDF, портал сохраняет его во временное приватное место и создаёт запись в базе со статусом Pending scan. Файл ещё не доступен для скачивания. Воркeр по очереди забирает файл, сканирует и обновляет запись до Clean или Blocked.
В интерфейсе клиент сразу видит появившийся документ с пометкой Pending. Отображаются имя файла и размер, чтобы пользователь понял, что загрузка прошла, но кнопка «Скачать» отключена до очистки. Если сканирование задерживается, портал может показывать «Мы проверяем файл. Попробуйте через минуту.»
Если сканер пометил документ как опасный, клиент увидит статус Blocked с коротким, нетехническим пояснением: «Этот файл не прошёл проверку безопасности.» Саппорт и админы получают отдельный вид с деталями и дальнейшими шагами. Они могут:
- оставить файл заблокированным и попросить новый файл
- удалить файл и зафиксировать причину
- пометить как ложноположительный только если политика это допускает
При спорах («я загрузил вчера, а вы его потеряли») важны хорошие логи. Храните временные метки: загрузка получена, сканирование начато, сканирование завершено, статус изменён и кто совершил действие. Также сохраняйте хэш файла, оригинальное имя, аккаунт загрузчика, IP и код результата сканера. В AppMaster Data Designer и простой Business Process помогут управлять этими статусами и полями аудита без раскрытия карантинных файлов обычным пользователям.
Частые ошибки, которые приводят к реальным уязвимостям
Большинство провалов безопасности при загрузках — это не хитрые хакерские трюки, а мелкие проектные ошибки, которые случайно превращают небезопасный файл в обычный документ.
Классическая проблема — гонка: приложение принимает загрузку и сразу возвращает URL для скачивания, и кто‑то скачивает файл до завершения сканирования. При сканировании файлов относите «загружен» и «доступен» к разным состояниям.
Повторяющиеся ошибки:
- Смешение чистых и карантинных файлов в одном бакете/папке и полагание на правила именования. Одна неверная настройка прав и карантин бессмысленен.
- Доверие расширениям файлов, MIME‑типа или проверкам на стороне клиента. Злоумышленник может переименовать всё в .pdf и UI этого не заметит.
- Отсутствие плана на время простоя сканера. Если сканер медленный или оффлайн, файлы будут «в ожидании», и команды начнут добавлять небезопасные ручные обходы.
- Воркеры, которые пропускают те же правила авторизации, что и основной API. Воркер с правом читать «любой файл» — тихое повышение привилегий.
- Публикация угадываемых ID (инкрементных) для карантинных объектов, даже если содержимое вроде бы защищено.
Тестирование — ещё одна брешь. Команды тестируют с несколькими маленькими чистыми файлами и считают задачу выполненной. Нужно пробовать большие загрузки, повреждённые файлы и зашифрованные документы — именно там сканеры и парсеры часто падают или зависают.
Простой реальный пример: пользователь загружает «contract.pdf», который на самом деле — переименованный исполняемый файл внутри архива. Если портал сразу раздаёт его или служба поддержки имеет доступ к карантину без надлежащих проверок, вы открыли путь доставки вредоносного файла другим пользователям.
Быстрый чек‑лист перед релизом
Перед выпуском сканирования на вирусы проверьте места, где команды часто думают «всё в порядке», а потом обнаруживают, что нет. Цель простая: небезопасный файл не должен стать читаемым просто потому, что кто‑то угадал URL, повторил запрос или использовал старую кэш‑ссылку.
Начните с пользовательского потока. Любое действие: скачивание, превью или «открыть файл» должно проверять текущий статус сканирования в момент запроса, а не только при загрузке. Это защитит от гонок, отложенных результатов сканирования и случаев, когда файл пересканируют.
Минимальный пред‑релизный чек‑лист:
- Карантинное хранилище приватно по умолчанию: нет публичного доступа к бакету, нет «любой с ссылкой», и нет прямой отдачи из raw объектного хранилища.
- Каждая запись файла имеет владельца (пользователь, команда или tenant) и явный жизненный цикл: pending, clean, infected, failed.
- Очередь сканирования и воркеры имеют ограниченные ретраи, правила бэкоффа и оповещения, когда элементы застревают или массово падают.
- Логи аудита для загрузок, результатов сканирования и попыток скачивания (включая заблокированные), с указанием кто, когда и почему.
- Ручное переопределение есть, но только для админов, с записью и ограничением по времени (нет тихой кнопки «пометить как чистый»).
Наконец, убедитесь, что вы видите систему насквозь. Вы должны уметь ответить: «Сколько файлов сейчас в ожидании сканирования?» и «Какие tenant‑ы видят ошибки?» В AppMaster моделируйте жизненный цикл файла в Data Designer и навязывайте проверки состояния в Business Process Editor, чтобы правила были консистентны в вебе и мобильных клиентах.
Следующие шаги: превращаем дизайн в рабочее приложение
Начните с записи точных состояний, в которых может находиться файл, и того, что разрешено в каждом состоянии. Держите модель простой и явной: «uploaded», «queued», «scanning», «clean», «infected», «scan_failed». Затем добавьте правила доступа рядом с каждым состоянием. Кто может видеть файл, скачивать его или удалять, пока он не доверенный?
Дальше выберите подход по объёму и ожиданиям пользователей. Inline проще объяснить, но может сделать загрузки медленными. Асинхронный метод лучше масштабируется для document‑heavy приложений, но добавляет состояние, очереди и UI «в ожидании».
Практичный путь от дизайна к сборке — прототипировать полный поток end‑to‑end с реалистичными документами (PDF, Office, изображения, архивы) и реальным поведением пользователей (множественные загрузки, отмены, повторы). Не останавливайтесь на «сканер работает». Проверьте, что приложение никогда не отдаёт карантинный файл, даже по случайности.
Простой план сборки на неделю:
- Определить состояния файлов, переходы и правила доступа на одной странице
- Выбрать inline, async или гибридный подход и задокументировать компромиссы
- Реализовать upload -> карантинное хранилище -> задача сканирования -> callback результата с аудит‑логами
- Построить UI‑состояния для пользователей (pending, blocked, failed, approved)
- Добавить мониторинг с первого дня: размер бэклога, частота ошибок и время до очистки
Если вы строите без кода, AppMaster поможет моделировать метаданные файлов (статус, владелец, checksum, метки времени), создавать экраны загрузки и проверки и оркестрировать рабочий процесс сканирования с бизнес‑логикой и очередной обработкой. Это даст возможность протестировать реальный продуктовый поток рано и затем укреплять важные части: права, разделение хранилищ и надёжные ретраи.
Наконец, решите численно, что значит «хорошо». Задайте пороги оповещений до запуска, чтобы вы заметили застрявшие сканы и растущее число ошибок раньше, чем об этом сообщат пользователи.


