Безопасный разбор CSV и JSON: защитите загрузки от вредоносных данных
Безопасный разбор CSV и JSON помогает предотвратить инъекции формул, повреждённые строки и всплески памяти при загрузках файлов пользователями.

Почему CSV и JSON, присланные пользователями, представляют риск
Загрузки от пользователей — это недоверенный ввод. Звучит очевидно, но это меняет всё. Внутренние данные обычно соответствуют вашим правилам, потому что их сгенерировал ваш код. Загруженные файлы приходят из неизвестных инструментов, с неизвестными настройками и иногда с неизвестным умыслом.
CSV и JSON кажутся безобидными, потому что это «просто текст». Но текст всё ещё может вызвать баги, создать плохие записи или спрятать содержимое, которое части вашей системы затем выполнят. Даже небольшой файл может нанести вред, если попадёт в слабое место, например в пограничный случай парсера или в формулу для таблицы.
Это выливается в реальные проблемы бизнеса несколькими типичными способами: импорты падают и вызывают простои, плохие строки тихо портят базу данных, экспорты приводят к CSV‑инъекциям формул при открытии в табличных приложениях, а крупные загрузки вызывают тормоза, когда вы пытаетесь загрузить всё в память.
«Безопасный разбор» не значит «мой код может прочитать файл». Это значит, что вы контролируете, что происходит, когда файл странный, враждебный или просто больше, чем ожидалось. На практике это сводится к границам (размер, время, число строк), предсказуемому чтению (потоковая обработка вместо загрузки всего за раз) и проверке каждой записи до того, как она попадёт в базу данных.
Реалистичный сценарий — импорт контактов, загруженный клиентом. Файл может содержать ячейку с 5 МБ строки, JSON‑поле с неожиданной вложенностью или имя, начинающееся с =HYPERLINK(...). Если вы будете относиться к загрузке как к доверённому вводу, вы рискуете простоем, долгой очисткой данных и инцидентом безопасности.
Основные сценарии отказов, о которых стоит позаботиться
Загрузки пользователей падают по нескольким предсказуемым причинам. Назовите их заранее, и безопасный разбор станет скучным, а не рискованным.
Повреждённый CSV — классическая проблема. Реальные файлы часто имеют битые кавычки, лишние запятые, неравное число колонок или странные кодировки, которые превращают символы в нечитаемую кашу. Некоторые парсеры пытаются угадать, что имел в виду пользователь. Это может молчаливо сдвинуть колонки и испортить данные или упасть при обработке строки с сильным повреждением.
CSV‑инъекция формул хитрее. Табличные приложения трактуют ячейки, начинающиеся с =, +, - или @, как формулы. Если вы потом экспортируете данные и кто‑то откроет их в Excel или Google Sheets, злоумышленник может поместить ячейку, которая выполнит формулу. Опасность часто проявляется позже: в человеческом рабочем процессе.
Подводные камни JSON проявляются иначе. Глубоко вложенные объекты могут достигать лимитов рекурсии или резко увеличить нагрузку на CPU. Огромные массивы превращают «малую загрузку» в минуты обработки. Дублирующиеся ключи — ещё один подлян: одни парсеры сохраняют первое значение, другие — последнее. Атакующие могут использовать эту неоднозначность, чтобы обойти валидацию.
Всплески памяти — самый простой сценарий отказа. Чтение всего файла в память (или построение целого объекта в памяти до валидации) превращает большие загрузки в таймауты и падения. 50 МБ загрузки могут стать намного больше после парсинга.
Практический пример: contacts.csv что‑то вроде нормального, но в одной строке незакрытая кавычка. Ваш парсер сдвигает колонки, и теперь поле email содержит части адреса. Или contacts.json включает контакт с огромным массивом заметок, и использование памяти взлетает.
Начните с жёстких границ до парсинга
Большинство багов при загрузке случаются до того, как парсер успевает помочь. Если вы принимаете «что‑то похожее на таблицу» или «какой‑нибудь JSON», вы приглашаете пограничные случаи, которые никогда не тестировали.
Решите, что вы реально поддерживаете. Если функция нужна только для простой таблицы, принимайте CSV и отвергайте JSON целиком (и наоборот). Поддержка меньшего числа форматов убирает много странных углов.
Затем введите жёсткие лимиты, соответствующие реальному использованию. Только размер файла мало что говорит. Небольшой файл всё ещё может содержать миллион крошечных полей, а файл нормального размера может разрастись в памяти, если вы загрузите его целиком.
Границы, которые стоит вводить
Для типичных импортов, например контактов, небольшой набор ограничений решает большую часть задач:
- Максимум байт (на основе ваших продуктовых лимитов)
- Максимум строк и максимум столбцов
- Максимальная длина поля (для каждой ячейки или JSON‑строки)
- Максимальная глубина вложенности для JSON
- Ограничение времени на разбор
Кодировка — ещё одна частая ловушка. Предпочитайте UTF‑8, обрабатывайте BOM и отвергайте файлы, которые нельзя корректно декодировать. Молчаливая «лучшее усилие» декодирования может превратить один битый байт в проблему со сдвигом разделителя, которая ломает все строки.
Отвергайте неожиданное содержимое как можно раньше. Для CSV валидируйте заголовок точно (или допускайте небольшой allowlist) перед обработкой строк. Для JSON подтвердите верхнеуровневую форму (объект или массив) и обязательные ключи до того, как тронете остальное.
Логируйте отказы, не сохраняя чувствительное содержимое. Сохраняйте метаданные и причины (размер файла, число строк, первая строка с ошибкой, правило, которое не прошло), но избегайте хранения сырой загрузки.
Защита от CSV‑инъекции формул
CSV выглядит безобидным, но табличные приложения интерпретируют некоторые ячейки как формулы. Если злоумышленник загрузит CSV, а вы потом экспортируете его для открытия человеком, значение, которое начинается как формула, может выполниться. Это и есть CSV‑инъекция формул.
Проблема обычно не в падении парсера. Опасность в том, что происходит после парсинга, когда человек открывает данные и таблица вычисляет формулу.
Практическое правило: храните оригинал для аудита, но создавайте безопасную для экспорта копию для любого места, откуда значение может оказаться в CSV, который откроют в табличном редакторе. Тогда вы не теряете то, что прислал пользователь, и не даёте потенциально опасный файл коллегам.
При создании экспортной копии считайте ячейку рискованной, если после удаления ведущих пробелов и табуляций она начинается с любого из символов: =, +, - или @. Если рискованная — нейтрализуйте, добавив префикс апострофа (') перед значением в экспортируемом CSV. Тогда большинство табличных приложений покажут текст, но не будут выполнять формулу.
Остерегайтесь хитрых вводов. Атакующие часто добавляют ведущие пробелы, табы или невидимые символы, чтобы значение выглядело нормальным, но всё ещё вычислялось как формула. Решите, какие ведущие символы вы считаете игнорируемыми (минимум пробелы и табы), и применяйте проверку к очищенной версии.
Проверьте кейсы, которые часто проскальзывают:
=HYPERLINK("example","click")+SUM(1,1)-2+3(похоже на вычисление)@SUM(1,1)\t=1+1(ведущий пробел или таб)
Сделать разбор JSON предсказуемым с помощью валидации
JSON от пользователя — это не просто данные. Это недоверенный ввод, и вы хотите, чтобы он всегда вел себя одинаково. Цель проста: отвергайте сюрпризы сразу и принимайте только ту форму, которую ожидает приложение.
Начните с валидации структуры до использования значений: обязательные поля, правильные типы и узкий набор разрешённых значений. Если эндпоинт ожидает что‑то вроде { "email": string, "role": "admin"|"member" }, не принимайте числа, массивы или дополнительные объекты «потому что они ещё как‑то работают». Так баги появляются в проде.
Вводите жёсткие лимиты для дорогостоящих JSON
JSON‑файл может быть мал по весу, но дорог в обработке: глубоко вложенные массивы или огромные строки. Задайте лимиты заранее:
- Максимальная глубина вложенности
- Максимальная длина строки для каждого поля (особенно заметок, биографий и метаданных)
- Максимальная длина массивов
- Максимальное число ключей в объекте
Эти проверки делают разбор предсказуемым и защищают память и CPU.
Рассматривайте дубликаты ключей как проблему
Входящие JSON могут содержать дублирующиеся ключи, и парсеры по‑разному их обрабатывают (первый побеждает, последний побеждает или поведение неопределено). Эта неоднозначность может использоваться для обхода валидации. Чаще всего безопаснее отвергать полезлоады с повторяющимися ключами.
Также избегайте «полезных» угадываний типов. Если вам нужен строковый тип, оставляйте его строкой. Не конвертируйте "00123" в число и не интерпретируйте даты автоматически — это меняет смысл и ломает downstream‑логику.
Когда валидация падает, возвращайте понятные, безопасные для пользователя ошибки, например contacts[12].email must be a valid email address. Не выводите трассировки стека, детали SQL или внутренние реализации.
Пошагово: потоковый разбор, который не упадёт
Краны чаще всего ломаются потому, что сервер пытается быть «полезным» и загружает весь файл в память. Более безопасный паттерн: сначала ставьте границы, затем обрабатывайте маленькими кусками.
Начните с быстрой проверки до парсинга. Проверьте заявленный content type, но не доверяйте ему. Введите жёсткий максимум по размеру файла и отвергайте сжатые файлы, если вы не можете их безопасно инспектировать. Также задайте лимит времени, чтобы одна медленная загрузка не занимала воркер.
Практичный потоковый сценарий
Простой поток, который подходит и для CSV, и для JSON:
- Предпроверка границ: макс. байт, допустимые кодировки и макс. число строк или объектов.
- Читайте поток с диска или из тела запроса. Не вызывайте «читать всё» и не стройте большую строку в памяти.
- Парсите запись за записью и останавливайтесь при достижении жёстких лимитов (строки, полей в строке, максимум глубины для JSON, макс. длина строки).
- Валидируйте каждую запись по ходу и собирайте лишь небольшой образец ошибок (например, первые 20 проблем) плюс счётчики.
- Записывайте принятые данные по частям: пакетные вставки или пуш валидных записей в очередь для дальнейшей обработки.
Не пытайтесь «править» сломанную структуру на лету. Если парсер сигнализирует о повреждении, остановитесь и завершите с ошибкой. Частичные импорты допустимы только если ваш продукт явно это объясняет.
Для удобства пользователя возвращайте сводку: сколько записей принято, сколько отклонено и короткий список примерных ошибок с номерами строк (или JSON‑путями). Например: «2431 импортировано, 17 отклонено. Топ ошибок: отсутствует email, неверная дата, лишние колонки.»
Правила валидации, которые держат плохие данные под контролем
Парсинг — это лишь первый шаг. Реальная выгода в безопасности и качестве приходит от отношения к каждой загрузке как к недоверенному вводу и проверки её по чёткому контракту, прежде чем данные коснутся базы.
Опишите, что значит «валидно» для вашего приложения. Делайте описание коротким и точным и применяйте его одинаково к колонкам CSV и полям JSON. Хороший контракт обычно охватывает разрешённые поля, обязательные и опциональные поля, правила по типам и форматам (email, телефон, ISO‑дата, валюта), ограничения по диапазону и длине.
Allowlist‑подход важен, потому что он не даёт неожиданным полям просочиться, например лишнему isAdmin в JSON или дополнительной колонке role в CSV. Если заголовок или ключ не в списке — либо отвергайте файл, либо явно игнорируйте поле и логируйте это, но не принимайте молча.
Нормализация должна быть аккуратной и предсказуемой. Обрезка пробелов и приведение TRUE к true нормальны, но избегайте преобразований, меняющих смысл. Для дат выберите один допустимый формат (или короткий перечень), нормализуйте в единый формат вывода и соблюдайте консистентность.
Сообщения об ошибках должны помочь пользователю быстро исправить файл. Вместо «некорректный ввод» возвращайте: «Строка 17, поле email: ожидается [email protected]». Для JSON указывайте путь: «contacts[3].phone отсутствует.»
Решите заранее, насколько строгими вы хотите быть. Всё‑или‑ничто безопаснее для импортов, где нужна консистентность. Частичный успех может подойти для списков контактов, но тогда нужны понятные правила (что отклоняется, сколько ошибок возвращаете и что сохраняете).
Частые ошибки и ловушки, которых стоит избегать
Большинство ошибок при загрузке — это не одна большая уязвимость, а цепочка мелких допущений.
Обычная ловушка — сначала парсить всё, а потом валидировать. К моменту отказа плохие данные уже могут быть в памяти, записаны во временную таблицу или переданы в бизнес‑логику. Рассматривайте валидацию как часть парсинга: отвергайте рано и переставайте читать, как только понимаете, что файл недопустим.
Ещё одна ошибка — доверять имени файла. Кто‑то может загрузить файл с именем contacts.csv, который на деле совсем другой формат, или CSV настолько повреждённый, что парсер ведёт себя странно. Инспектируйте содержимое (заголовки, разделители, первые байты) и вводите небольшую допустимую форму до того, как начать тяжёлую обработку.
Типичные повторяющиеся ловушки:
- Разрешать библиотеке автоопределять типы без лимитов. Угадывание может превратить странный ввод в гигантские числа, даты или
NaN. - Повторный экспорт сырого CSV без защиты от формул. Хранение
=HYPERLINK(...)— одно, а экспорт для коллеги — совсем другое. - Загрузка всего файла «чтобы получить лучшие ошибки». Одна переразмеренная загрузка может вызвать всплеск памяти и таймаут.
- Возвращение массивных отчётов об ошибках. Перечисление каждой плохой строки для огромного файла может создать вторую проблему с памятью и выдать фрагменты чувствительных данных.
Реалистичный пример: вы импортируете leads.csv и возвращаете детальную ошибку по каждой строке. Атакующий загружает большой файл с мелкими ошибками на каждой строке. Сервер тратит минуты на сбор ошибок, собирает многомегабайтный ответ и умирает.
Быстрый чек‑лист безопасности для CSV и JSON загрузок
Если вы хотите безопасный разбор CSV и JSON, предполагайте, что файл враждебен. Большинство багов при загрузке — это простые неожиданные вводы: огромные файлы, странные кодировки или поля, которые выглядят безобидно, но вызывают поведение в других инструментах.
Держите короткий чек‑лист как ворота перед тем, как данные попадут в базу:
- Установите жёсткие лимиты: макс. байт, макс. строк/объектов, макс. столбцов/полей и (для JSON) макс. глубина вложенности.
- Парсите потоково, чтобы одна загрузка не подняла память.
- Валидируйте структуру и значения: обязательные поля, типы, лимиты длины, разрешённые enum, и форматы дат/чисел.
- Нейтрализуйте CSV‑инъекции формул при экспорте или пересохранении данных.
- Fail closed: отвергайте при ошибках парсинга и возвращайте маленькие понятные сообщения.
Для JSON «валидный JSON» недостаточен. Большой файл с глубоко вложенными массивами может быть валидным и при этом убить производительность. Ограничения по глубине, длине полей и строгая схема делают разбор предсказуемым.
После релиза мониторьте базовые метрики: таймауты парсинга и медленные разборы, частоту отказов по причинам и среднее число строк/объектов на загрузку. Эти сигналы подскажут, где стоит подправить лимиты и UX.
Реалистичный пример: безопасный импорт контактов
Основатель добавляет шаг «Импорт контактов» в поток регистрации. Пользователи могут загрузить CSV из Excel или JSON‑экспорт из другого инструмента. Цель проста: создать записи контактов (имя, email, компания) и пропускать всё небезопасное.
Однажды в колонке first‑name приходит:
=HYPERLINK("example","Click me")
Если ваше приложение потом экспортирует эти контакты в CSV для коллеги, такая ячейка может выполниться как формула. Решение не в «очистке позже». Для любого поля, которое может быть записано обратно в CSV, либо отвергайте значения, начинающиеся с =, +, - или @, либо сохраняйте безопасную версию для экспорта (например, с апострофом спереди) и не включайте оригинал в экспорт.
В другой день кто‑то загружает большой JSON. Если сервер читает его в память и парсит целиком, он может зависнуть или упасть. Вместо этого вводите лимит размера загрузки, парсите потоково, обрабатывайте контакт за контактом и прекращайте разбор при превышении лимитов (макс. записей, макс. длина поля, макс. вложенность).
Что видит пользователь — важно. Страница импорта должна показывать понятную сводку: сколько импортировано, сколько пропущено, и небольшой набор причин с номерами строк или JSON‑путями. В логах должно быть достаточно информации для отладки без хранения персональных данных. Логируйте счётчики, номера строк, коды ошибок и нечувствительные хеши, чтобы замечать повторения без хранения сырого содержимого.
Следующие шаги: ужесточите пайплайн и попросите помощи, если нужно
Относитесь к загрузкам как к внешней интеграции. Постройте небольшой набор правил, которые можно проверить.
Аудитируйте эндпоинты, принимающие файлы. Ищите явные лимиты по размеру, таймауты, потоковый разбор и места, где файл читается дважды или конвертируется в гигантскую строку перед разбором.
Держите небольшой набор тестов, который запускаете при каждом изменении: повреждённый CSV, CSV, проверяющий инъекции формул, глубоко вложенный JSON, JSON с неправильными типами и отсутствующими полями, и большой валидный файл на границе вашего лимита. Эти несколько файлов ловят большинство регрессий.
Если вы имеете дело с AI‑сгенерированным кодом (особенно прототипами из Lovable, Bolt, v0, Cursor или Replit), сделайте дополнительный проход на предмет пропущенных лимитов, чтения целиком файла и сырых логов. Если хотите второе мнение, FixMyMess (fixmymess.ai) помогает командам диагностировать и исправлять ломанные AI‑сгенерированные потоки импорта, включая добавление границ, валидации и безопасного экспорта.
Часто задаваемые вопросы
Почему загрузки CSV и JSON опасны, если это «просто текст»?
Рассматривайте каждую загрузку как недоверенный ввод. Даже «просто текст» может вызвать пограничные случаи парсера, создать некорректные записи или вызвать проблемы позже, когда данные экспортируют и открывают в табличном редакторе.
Стоит ли пытаться «автоматически исправлять» сломанные CSV?
Строгий разбор предотвращает молчаливые сдвиги данных. Если вы пытаетесь угадать, что пользователь «имел в виду», одна незакрытая кавычка или лишняя запятая могут сдвинуть значения в нужные колонки и незаметно испортить базу данных.
Какие лимиты стоит ввести до того, как парсить загруженный файл?
Задайте лимиты до парсинга: максимальный размер в байтах, максимальное число строк/столбцов и максимальную длину поля. Одного ограничения по размеру файла часто недостаточно: скромный по весу файл может содержать гигантские поля или сильно увеличиться в памяти при разборе.
Как предотвратить падение приложения из‑за переполнения памяти при загрузках?
Читайте потоково и валидируйте по мере чтения. Если вы сначала считаете весь файл в память, крупная загрузка (или небольшой файл, который разрастается при разборе) может спровоцировать всплеск памяти и падение воркера или таймаут.
Что такое CSV‑инъекция формул и когда она действительно опасна?
Это значение, которое начинается с =, +, - или @ и при экспорте и открытии в Excel/Google Sheets может быть выполнено как формула. Риск обычно проявляется позже в человеческом рабочем потоке, а не во время импорта.
Как проще всего защищать экспорт от CSV‑инъекций формул?
Сохраняйте оригинальное значение для хранилища и аудита, но для любого CSV, который кто‑то откроет в табличном редакторе, генерируйте безопасную для экспорта копию. Частый подход — после удаления ведущих пробелов/табуляций добавлять один апостроф ('), если значение начинается с символа формулы.
Как сделать разбор JSON предсказуемым и безопасным?
Валидируйте строго ожидаемую форму до использования значений и отвергайте сюрпризы сразу. Введите жёсткие лимиты на глубину вложенности, длину строк, длину массивов и количество ключей, чтобы «валидный JSON» не мог быть дорогостоящим для разбора.
Стоит ли отвергать JSON с дублирующимися ключами?
Поскольку парсеры по‑разному обрабатывают дубликаты ключей (побеждает первый, последний или поведение не определено), это может быть использовано для обхода валидации. Самый безопасный подход — отвергать объекты JSON с повторяющимися ключами.
Какие сообщения об ошибках показывать при неудачной валидации импорта?
Возвращайте понятное, небольшое резюме, которое пользователь может исправить: какая строка/путь не прошли и почему. Не выдавайте сырые чувствительные данные, трассировки стека или огромные отчёты по каждой строке — это создаст вторую проблему с производительностью.
Какие самые частые ошибки в AI‑сгенерированном коде импорта и как FixMyMess может помочь?
Ищите чтение целиком файла в память, отсутствие лимитов по размеру/времени, запись сырых загрузок в логи и экспорт, который пересохраняет пользовательские данные в CSV без защиты от формул. Если код импорта сгенерирован ИИ и ведёт себя ненадёжно, FixMyMess может быстро провести аудит и починить границы, потоковый разбор, валидацию и безопасный экспорт, чтобы всё работало предсказуемо.