JSON является одним из наиболее популярных форматов обмена данными благодаря своей простоте и удобочитаемости. В экосистеме Ruby за работу с JSON отвечает JSON gem — библиотека, предоставляющая широкий набор инструментов для парсинга и генерации JSON. Несмотря на её распространённость, API JSON gem содержит ряд серьёзных проблем и опасных моментов, которые часто остаются незамеченными, но могут привести к уязвимостям или неожиданному поведению приложения. Одной из самых острых проблем, ставших причиной пересмотра API и инициатив по поддержке библиотеки, является опция create_additions, активируемая по умолчанию в методе JSON.load.
Она отвечает за возможность «оживления» объектов, то есть десериализации JSON-объектов в Ruby-объекты определённых классов через метод .json_create. Эта функциональность, казалось бы, удобна, ведь позволяет автоматически восстанавливать сложные объекты из JSON. Однако она же и открывает широкое поле для потенциальных атак. Суть уязвимости кроется в глобальном характере этой функции: при наличии в JSON-строке специального ключа "json_class" библиотека пытается найти соответствующий класс и вызвать на нём метод .
json_create. Злоумышленник, модифицируя JSON, может добиться создания нежелательных объектов, что в худших случаях приведёт к выполнению произвольного кода. Более того, даже если в коде приложения не объявлены небезопасные реализации .json_create, библиотека сама определяет такой метод для класса String, что способствует обходу различных механизмов валидации. Подобный подход напоминает старые проблемы с YAML, где подобные «магические» методы активно использовались для скрытых атак.
В связи с этим эксперты в области безопасности и стилистики кода рекомендуют избегать использования JSON.load с включённым create_additions. В новых версиях JSON gem implicit использование этой опции помечено как устаревшее и вызывающее предупреждение, а для безопасного использования подобной функциональности рекомендуется явно использовать JSON.unsafe_load. Параллельно с этим механизмом был введён альтернативный способ обработки сложных объектов — через callback-функции (Proc), которые вызываются для каждого элемента при парсинге.
Такой подход предоставляет гораздо большую гибкость и безопасность, позволяя разработчику контролировать процесс десериализации локально, а не глобально через магические методы классов. Это решение открывает возможность создавать собственные логики обработки JSON-данных без риска нежелательных последствий в масштабах всего приложения. Ещё одна заметная проблема связана с неверной обработкой дубликатов ключей в объектах JSON. В спецификациях JSON прямо не прописано, как парсер должен поступать с повторяющимися ключами. Большинство реализаций, включая JSON gem, по умолчанию сохраняют последний встретившийся ключ, перезаписывая предшествующее значение.
Несмотря на это, такая практика может быть источником ошибок и уязвимостей. Примером служит инцидент в компании Hacker One, где проблема с генерацией некорректного JSON позволила скрыть баг на ранней стадии разработки. В новых версиях JSON gem реализована строгая политика по отношению к повторяющимся ключам: по умолчанию при их обнаружении выводится предупреждение, а в будущих релизах это приведёт к ошибке и прерыванию парсинга. При необходимости допускается явное разрешение дублей через параметр allow_duplicate_key: true. Такая смена поведения заставляет разработчиков внимательно проверять данные и предупреждает случайные ошибки, которые могут легко пройти незамеченными.
Ещё одним аспектом, вызывающим споры и дискуссии, является способ сериализации нестандартных объектов в JSON. На сегодняшний день, чтобы объект умел корректно преобразовываться в JSON, он должен реализовывать метод to_json. Однако этот подход имеет серьёзные минусы. Во-первых, изменение метода to_json для глобальных классов, например Time, влияет на сериализацию во всём приложении, что может приводить к конфликтам между библиотеками и неожиданному поведению. Кроме того, поведение JSON при сериализации незнакомых типов, не имеющих собственного to_json, сводится к вызову to_s и вставке полученной строки в JSON.
Это часто приводит к созданию некорректных JSON-документов или к неожиданным результатам, если to_s возвращает человекочитаемое, но не JSON-совместимое представление объекта. В качестве решения предлагается новый класс JSON::Coder, который жёстко ограничивает сериализуемые типы. Он по умолчанию не позволяет сериализовать произвольные объекты, давая ошибку, если тип не поддерживается. Для поддержки специальных типов JSON::Coder предусматривает приём блока, позволяющего задать правила преобразования конкретных классов в JSON-совместимое значение. Такой подход обеспечивает изоляцию поведения и предотвращает глобальные изменения стандартной сериализации, делая код более предсказуемым и безопасным.
Еще один дискутируемый момент — глобальные настройки JSON gem, такие как load_default_options и dump_default_options, которые позволяют вносить глобальные изменения в поведение парсинга и генерации JSON. На первый взгляд, это удобно — можно настроить нужные параметры один раз и не беспокоиться о передаче их дальше по всему проекту. Но с точки зрения архитектуры это напоминает монкипатчинг и глобальное состояние, что существенно усложняет отладку, снижает предсказуемость и может привести к неожиданным ошибкам в сложных экосистемах, где несколько библиотек работают с JSON. Чтобы минимизировать эти риски, современный тренд в сообществе — переводить подобные настройки из глобальных в локальные конфигурации. JSON::Coder, рассмотренный выше, служит примером такого подхода, позволяя создавать отдельные экземпляры с кастомной конфигурацией, используемые независимо друг от друга.
Наконец, стоит отметить, что процесс постепенной депрекации устаревших API в JSON gem проходит аккуратно с учётом пользовательского опыта. Переход сопровождается понятными предупреждениями и рекомендациями, а большинство нововведений направлено на повышение безопасности, наглядности и управляемости кода. При этом ключевые методы, такие как Object#to_json, ни в коем случае не планируется отменять, из-за их широкой распространённости и глубокой интеграции в Ruby-сообщество. Подводя итог, можно сказать, что основные проблемы API JSON gem связаны с глобальностью и непрозрачностью некоторых функций, а также с возможными рисками безопасности, вызванных удобными на первый взгляд, но опасными механизмами. Переход на новые подходы — явные опции, callback-функции для десериализации, локальная настройка сериализации — повышает безопасность и надёжность работы с JSON в Ruby.