<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Security | Toparvion.Pro</title><link>https://toparvion.pro/category/security/</link><atom:link href="https://toparvion.pro/category/security/index.xml" rel="self" type="application/rss+xml"/><description>Security</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>ru-ru</language><lastBuildDate>Sun, 12 Apr 2026 20:31:05 +0700</lastBuildDate><image><url>https://toparvion.pro/media/sharing.jpg</url><title>Security</title><link>https://toparvion.pro/category/security/</link></image><item><title>Анализируем heap-дампы с прода, не привлекая внимания безопасников</title><link>https://toparvion.pro/post/2026/hprof-redaction/</link><pubDate>Sun, 12 Apr 2026 20:31:05 +0700</pubDate><guid>https://toparvion.pro/post/2026/hprof-redaction/</guid><description>&lt;p> &lt;/p>
&lt;details class="toc-inpage d-print-none " open>
&lt;summary class="font-weight-bold">Содержание&lt;/summary>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>
&lt;ul>
&lt;li>&lt;a href="#введение">Введение&lt;/a>&lt;/li>
&lt;li>&lt;a href="#способ-1-eclipse-mat">Способ 1. Eclipse MAT&lt;/a>&lt;/li>
&lt;li>&lt;a href="#способ-2-heap-dump-tool">Способ 2. Heap Dump Tool&lt;/a>&lt;/li>
&lt;li>&lt;a href="#способ-3-hprof-redact">Способ 3. Hprof-redact&lt;/a>&lt;/li>
&lt;li>&lt;a href="#способ-4-jdk">Способ 4. JDK&lt;/a>&lt;/li>
&lt;li>&lt;a href="#заключение">Заключение&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/details>
&lt;p>Heap-дампы JVM – бесценный источник информации при разборе аварий с OutOfMemory и оптимизации производительности. Но вместе с тем они же – потенциальные каналы утечки данных, ведь будучи снятыми с боевого сервиса, дампы уносят в себе всё, с чем работал сервис на момент снимка: логины, пароли (иногда в открытом виде), важные ID и т.п. – словом, всяческие sensitive данные, которые не нужны для анализа, но которые навлекают на получателя дампа серьёзную ответственность и риски. Как этого избежать без ущерба делу – разбираемся в этой статье.&lt;/p>
&lt;h3 id="введение">Введение&lt;/h3>
&lt;p>Для начала возьмём лабораторный, но реалистичный пример – &lt;a href="https://github.com/spring-petclinic/spring-petclinic-rest" target="_blank" rel="noopener">Spring PetClinic REST&lt;/a> – бэкендовую версию популярного демо-приложения. Эта версия из коробки &lt;a href="https://github.com/spring-petclinic/spring-petclinic-rest?tab=readme-ov-file#security-configuration" target="_blank" rel="noopener">включает&lt;/a> Spring Security, который, в свою очередь, при каждом запуске генерирует пароль для ограничения доступа к методам API. И хотя пароль там “игрушечный” (логируется в открытом виде на старте приложения), механизм его попадания в память и дальнейшего распространения вполне соответствует реальным кейсам у других конфиденциальных данных, поэтому для примера он нам подходит.&lt;/p>
&lt;p>Прежде чем искать варианты решения проблемы, надо её &lt;strong>увидеть&lt;/strong>. Благо, на выбранном примере для этого достаточно:&lt;/p>
&lt;ol>
&lt;li>Склонировать репозиторий:&lt;br>
&lt;code>git clone https://github.com/spring-petclinic/spring-petclinic-rest.git&lt;/code>&lt;/li>
&lt;li>Открыть его в OpenIDE или другой среде разработки&lt;/li>
&lt;li>Открыть там же (или отдельно) терминал и выполнить:&lt;br>
&lt;code>./mvnw spring-boot:run&lt;/code>.&lt;/li>
&lt;/ol>
&lt;p>Эта команда запустит PetClinic с дефолтными настройками (со встроенной БД).&lt;/p>
&lt;p>Дальше снимаем дамп памяти и открываем его для анализа, например, с помощью &lt;a href="https://eclipse.dev/mat/download/" target="_blank" rel="noopener">Eclipse MAT&lt;/a> (&lt;code>File -&amp;gt; Acquire Heap Dump…&lt;/code>):&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img src="./img/mat-plain.png" alt="image-20260324095245339" loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>Среди этих полумиллиона объектов конфиденциальными данными может оказаться что угодно, поэтому ни в Eclipse MAT, ни в других популярных opensource-инструментах нет волшебной кнопки “&lt;em>Покажи мне все возможные утечки&lt;/em>”. Зато в них (в частности, в MAT и VisualVM) есть возможность выполнять SQL-подобные запросы к дампу, выуживая таким образом наиболее подозрительные объекты для проверки.&lt;/p>
&lt;p>Основная идея этого подхода в том, что каждый класс представляется как таблица, каждое поле класса – как столбец таблицы, а каждый экземпляр – как её строка. Подробнее об этом можно узнать, например, из моего &lt;a href="https://toparvion.pro/event/2024/joker/" target="_blank" rel="noopener">доклада&lt;/a> на Joker 2024.&lt;/p>
&lt;p>В данном случае из описания и исходников проекта мы знаем, что Spring Security используется в самом простом режиме, когда все данные для доступа хранятся прямо в памяти, а сами явки-пароли представлены классом &lt;code>org.springframework.boot.autoconfigure.security.SecurityProperties.User&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">User&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * Default user name.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * Password for the default user name.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">UUID&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">randomUUID&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">toString&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Этого достаточно, чтобы составить вот такой OQL–запрос к дампу:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">toString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">User&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">toString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Password&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">org&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">springframework&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">boot&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">security&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">autoconfigure&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SecurityProperties$User&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Запрос вернёт логин и пароль, действовавший на момент снятия дампа:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img src="./img/plain-creds.png" alt="image-20260324101646494" loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>&lt;strong>&amp;#x1f3af; Вот от этих данных нам и нужно избавиться перед передачей дампа кому бы то ни было.&lt;/strong>&lt;/p>
&lt;p>Аналогичным образом, опираясь на знания бизнес-логики и внутреннего устройства приложения, можно составить несколько OQL-запросов, точечно проверяющих присутствие sensitive данных в дампе.&lt;/p>
&lt;p>А дальше нужно понять, как и чем их можно вычистить.&lt;/p>
&lt;h3 id="способ-1-eclipse-mat">Способ 1. Eclipse MAT&lt;/h3>
&lt;p>Раз уж мы упомянули &lt;a href="https://eclipse.dev/mat/" target="_blank" rel="noopener">Memory Analyzing Tool&lt;/a>, было бы логично прояснить его возможности по устранению конфиденциальных данных в дампах. Возможности такие у него есть, правда, расположены не в самом очевидном месте – нужно вызвать контекстное меню какого-либо элемента дампа и выбрать &lt;strong>Export Snapshot&lt;/strong>:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="image-20260325214751708" srcset="
/post/2026/hprof-redaction/img/export-snapshot_hu5d1ed8f8df137f5d862dec5789b4d1e9_24350_146d8abf4869de969b758fbc3b7257c8.webp 400w,
/post/2026/hprof-redaction/img/export-snapshot_hu5d1ed8f8df137f5d862dec5789b4d1e9_24350_4d1ef846b4a59a534cbf8af855bf0e5b.webp 760w,
/post/2026/hprof-redaction/img/export-snapshot_hu5d1ed8f8df137f5d862dec5789b4d1e9_24350_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://toparvion.pro/post/2026/hprof-redaction/img/export-snapshot_hu5d1ed8f8df137f5d862dec5789b4d1e9_24350_146d8abf4869de969b758fbc3b7257c8.webp"
width="545"
height="423"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>Вообще, это довольно мощная фича MAT’а, с помощью которой можно выделять из дампа различные срезы, всячески их преобразовывать и сохранять в виде нового дампа так, будто он изначально был снят таким. Как не трудно догадаться, на эти рельсы хорошо ложится и обфускация данных. Для неё в диалоге экспорта предусмотрено несколько параметров. Главный из них – &lt;strong>redact&lt;/strong> – поддерживает следующие значения:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>NONE&lt;/strong> – не меняет ничего;&lt;/li>
&lt;li>&lt;strong>NAMES&lt;/strong> – пытается обфусцировать только имена классов, полей и методов. Работает в связке с параметром &lt;strong>map&lt;/strong> (см. ниже). Здесь ключевое слово “пытается”, потому что этот подход сопряжен с кучей условностей и ограничений. Подробнее см. в &lt;a href="https://help.eclipse.org/latest/topic/org.eclipse.mat.ui.help/tasks/exportdump.html?cp=40_3_13" target="_blank" rel="noopener">справке&lt;/a> на MAT.&lt;/li>
&lt;li>&lt;strong>BASIC&lt;/strong> – обнуляет все массивы &lt;code>char&lt;/code>, &lt;code>int&lt;/code> и &lt;code>byte&lt;/code>, а также поля классов с типами &lt;code>char&lt;/code> и &lt;code>byte&lt;/code>. Это позволяет устранить большинство строковых паролей, а также приватных ключей, которые нередко представлены объектами &lt;code>BigInteger&lt;/code>. Но оставляет нетронутыми поля всех остальных примитивных типов, а также их массивов, что может быть как полезно для анализа, так и опасно с т.з. утечек. Если же безопасность превыше всего, то есть вариант:&lt;/li>
&lt;li>&lt;strong>FULL&lt;/strong> – обнуляет вообще все поля и массивы, а заодно и &lt;code>false&lt;/code>-ифицирует все &lt;code>boolean&lt;/code>&amp;lsquo;ы. Щадит только ссылки на объекты и размеры массивов, чтобы не ломать структуру дампа и сохранить его пригодность для анализа.&lt;/li>
&lt;/ul>
&lt;p>Второй релевантный нам параметр – &lt;strong>map&lt;/strong> – указывает на &lt;code>properties&lt;/code>-файл, в который прописываются соответствия между оригинальными именами классов приложения и их обфусцированными версиями. Это может пригодиться, если вы собираетесь отдать дамп на анализ внешнему подрядчику и не хотите, чтобы он знал, чем занимается ваше приложение. Eclipse MAT генерирует этот файл сам, но если у вас есть лишний рабочий день и вы хотите наверняка сбить с толку вероятного противника, можете прописать эти сопоставления сами: для рассматриваемого примера их понадобится всего 77,5 тысяч. Столько набегает за счёт того, что в маппинг также попадают имена статических полей, а ещё сгенерированные и анонимные классы (включая лямбды). Выглядит это примерно так:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-properties" data-lang="properties">&lt;span class="line">&lt;span class="cl">&lt;span class="na">org.springframework.boot.web.server.WebServer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">xod.kloblaiwaiwreen.quak.lur.thiarm.Seotreure&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">org.jspecify.annotations.NonNull&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">xod.sheopluy.pouprukliel.Trawuak&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">org.h2.engine.User@passwordHash&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">xod.gj.exiosy.Knud@tuhoucudruag&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">jdk.proxy3.$Proxy105@m9&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">vuz.briarb.$Fliwoorm@EI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">io.swagger.v3.core.util.PrimitiveType@NUMBER&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">ar.gioliob.is.wras.kind.Graclanaiflob@KREART&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">org.apache.logging.log4j.MarkerManager$$Lambda&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">xod.froark.pooniek.luand.Cequiayuaniats$Klourt&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Чтобы не перестараться и не сломать парсинг самому себе, Eclipse MAT по умолчанию исключает из обфускации классы пакетов &lt;code>java.*&lt;/code> и даёт исключить другие через параметр &lt;strong>skip&lt;/strong> (регулярное выражение).&lt;/p>
&lt;p>Наконец, чтобы провернуть обратную операцию, т.е. восстановить дамп с исходными именами классов, можно воспользоваться параметром &lt;strong>undo&lt;/strong>, а именно:&lt;/p>
&lt;ol>
&lt;li>Открыть обфусцированный дамп в MAT&lt;/li>
&lt;li>Снова выбрать пункт &lt;em>Export Snapshot&lt;/em>&lt;/li>
&lt;li>Указать путь для сохранения восстановленного дампа&lt;/li>
&lt;li>Указать путь к &lt;code>properties&lt;/code>-файлу с маппингом (он будет только читаться)&lt;/li>
&lt;li>Поставить галочку &lt;strong>undo&lt;/strong> (параметр &lt;strong>redact&lt;/strong> должен остаться в &lt;strong>NONE&lt;/strong>)&lt;/li>
&lt;/ol>
&lt;p>Таким образом можно не хранить у себя исходную копию дампа с оригинальными именами классов, а при необходимости воссоздать её из обфусцированной версии, используя файл маппинга в качестве своеобразного “ключа восстановления”. Только важно помнить, что этот финт не годится для полей/массивов/примитивов внутри классов, так как если они были обфусцированы режимами &lt;strong>redact=BASIC&lt;/strong> или &lt;strong>FULL&lt;/strong>, то этот фарш назад уже не провернуть…&lt;/p>
&lt;p>Давайте вспомним, что наша основная задача – вычистить пароль Spring Security, а не засекретить все имена классов, поэтому проверим наличие пароля в дампе тем же OQL-запросом после обфускации в режиме &lt;strong>redact=BASIC&lt;/strong>:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="image-20260406192842747" srcset="
/post/2026/hprof-redaction/img/image-20260406192842747_hu293be765511381c9f628d1c123bf04b7_3105_dcd36cfc00296d434ece1869b93819a9.webp 400w,
/post/2026/hprof-redaction/img/image-20260406192842747_hu293be765511381c9f628d1c123bf04b7_3105_c47a9454972f27259963ca8231bd387c.webp 760w,
/post/2026/hprof-redaction/img/image-20260406192842747_hu293be765511381c9f628d1c123bf04b7_3105_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://toparvion.pro/post/2026/hprof-redaction/img/image-20260406192842747_hu293be765511381c9f628d1c123bf04b7_3105_dcd36cfc00296d434ece1869b93819a9.webp"
width="760"
height="87"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>Как видно, здесь теперь царит безопасность…&lt;/p>
&lt;p>В целом, неплохо, но смущает, что для экспорта дампа нам потребовалось открыть его исходную версию и что-то поделать с ней руками, а значит, мы могли увидеть в ней “лишние” данные, что уже влечёт за собой риски, ответственность и вот это всё. К счастью, Eclipse MAT поддерживает т.н. &lt;a href="https://help.eclipse.org/latest/topic/org.eclipse.mat.ui.help/tasks/batch.html?cp=40_3_14" target="_blank" rel="noopener">Batch Mode&lt;/a> – возможность выполнять некоторые операции без пользовательского ввода, то есть через CLI. Входной точкой этого режима является скрипт &lt;code>ParseHeapDump.[sh|bat]&lt;/code> в корневой директории MAT. Как подсказывает имя, изначально скрипт предназначен для автономного парсинга (больших) дампов, но его же можно использовать для других задач, указывая их имена в качестве аргументов. Для экспорта дампа с обфускацией это может выглядеть примерно так:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">./ParseHeapDump.sh plain.hprof -output&lt;span class="o">=&lt;/span>redacted.hprof -redact&lt;span class="o">=&lt;/span>BASIC org.eclipse.mat.hprof:export
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Здесь же можно указывать и другие опции из диалога &lt;em>Export Heap Dump…&lt;/em>, например, файл маппинга имен классов &lt;code>-map=myheapdump2.map&lt;/code>. А если и этого мало, то можно вызвать всё это программно через &lt;a href="https://help.eclipse.org/latest/topic/org.eclipse.mat.ui.help/doc/org/eclipse/mat/hprof/ExportHprof.html" target="_blank" rel="noopener">Memory Analyzer API&lt;/a> и докрутить нужные поведения уже на уровне Java.&lt;/p>
&lt;div class="alert alert-info">
&lt;div>
Для тех, у кого приведенные здесь манипуляции вызвали больше вопросов, чем понимания, или кто в принципе мало знаком с дампами и их анализом в Eclipse MAT, есть &lt;a href="https://java-perf-training.github.io/#plan" target="_blank" rel="noopener">специальный тренинг&lt;/a>, где можно не только разобраться с этой темой в теории, но и закрепить знания на практике с лабораторным приложением.
&lt;/div>
&lt;/div>
&lt;p>И вот, казалось бы, у нас есть всё, что нужно – и гибкие настройки обфускации, и автономный режим, и, конечно, желаемый результат. Но за всё это приходится платить, и валюта здесь – память и время:&lt;/p>
&lt;ul>
&lt;li>для работы с дампом Eclipse MAT должен его распарсить, а для этого ему нужно, в среднем, столько же &lt;strong>оперативной памяти&lt;/strong>, сколько весит сам дамп;
&lt;ul>
&lt;li>как парсить большие дампы при малом объёме RAM, можно почитать в &lt;a href="https://t.me/stopshelf/227" target="_blank" rel="noopener">этой заметке&lt;/a>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>при парсинге MAT производит много вспомогательных индексов, размер которых может &lt;em>в разы&lt;/em> превосходить размер исходного дампа, поэтому нужно много &lt;strong>места на диске&lt;/strong>;
&lt;ul>
&lt;li>при этом удалить исходный дамп после парсинга нельзя – он всё равно остаётся частью общей структуры данных;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>парсинг состоит из множества этапов, включающих многократные обходы исходного дампа, поэтому на больших объёмах это может занимать &lt;strong>много времени&lt;/strong>;
&lt;ul>
&lt;li>у автора этих строк был период оптимизации одного сервера (64 GB RAM), когда можно было делать фиксы не чаще раза в день, потому что каждый новый дамп парсился по 4-5 часов (по ночам).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>Да и сам Eclipse MAT – тот ещё комбайн: его дистрибутив весит под 100 МБ, поэтому если стоит задача включить его в состав CI/CD в качестве автоматического этапа обфускации дампов, полученных с боевого сервера, то этот инструмент – не лучший выбор. К счастью, есть альтернативы.&lt;/p>
&lt;h3 id="способ-2-heap-dump-tool">Способ 2. Heap Dump Tool&lt;/h3>
&lt;p>Компания PayPal, тесно работая с платёжными данными и имея часть приложений на JVM, вероятно, много сталкивалась с необходимостью анализа “опасных” heap-дампов, поэтому разработала для этого специальный инструмент &lt;strong>&lt;a href="https://github.com/paypal/heap-dump-tool" target="_blank" rel="noopener">heap-dump-tool&lt;/a>&lt;/strong> и выложила его в open source.&lt;/p>
&lt;p>Это CLI-инструмент под конкретную задачу – на вход подаёте исходный дамп, на выходе получаете “дезинфицированный”:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ java -jar heap-dump-tool.jar sanitize leak.hprof sanitized.hprof
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO Application - heap-dump-tool &lt;span class="o">(&lt;/span>1.3.4 ca0325a, 2025-11-29T02:48:26.000-0800&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO SanitizeCommandProcessor - Pre-processing ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO SanitizeCommandProcessor - String fields to exclude from sanitization: java.lang.Thread#name,java.lang.ThreadGroup#name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO SanitizeCommandProcessor - Force match String.coder: &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO SanitizeCommandProcessor - Input File: leak.hprof
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO SanitizeCommandProcessor - Starting heap dump sanitization ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO SanitizeCommandProcessor - Input File: leak.hprof
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO SanitizeCommandProcessor - Output File: sanitized.hprof
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">INFO SanitizeCommandProcessor - Finished heap dump sanitization in 13s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Если на получившемся дампе выполнить тот же OQL-запрос на выявление пароля, то он выдаст примерно следующее:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="image-20260409220437471" srcset="
/post/2026/hprof-redaction/img/image-20260409220437471_hu9b1fafd951d848e8205d2d947e658e75_3178_00ec43d67d6ecbd1548ac9780ba0966d.webp 400w,
/post/2026/hprof-redaction/img/image-20260409220437471_hu9b1fafd951d848e8205d2d947e658e75_3178_40393c2d952e9020cdab1d7b5d040200.webp 760w,
/post/2026/hprof-redaction/img/image-20260409220437471_hu9b1fafd951d848e8205d2d947e658e75_3178_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://toparvion.pro/post/2026/hprof-redaction/img/image-20260409220437471_hu9b1fafd951d848e8205d2d947e658e75_3178_00ec43d67d6ecbd1548ac9780ba0966d.webp"
width="743"
height="99"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>, т.е. сплошную безопасность.&lt;/p>
&lt;p>Однако это не вся картина – на самом деле, по умолчанию Heap Dump Tool очищает &lt;strong>только байтовые и символьные массивы&lt;/strong>, а целочисленные, дробные и другие значения (в том числе массивы) сохраняет нетронутыми. Это может оставить лазейку для утечки, если, например, в каком-то целочисленном поле хранился персональный ID клиента или сумма денег на его счету (кое-где есть практика хранить денежные суммы в минимальных единицах валюты). Для таких случаев при вызове утилиты нужно добавить флажок &lt;code>--sanitize-byte-char-arrays-only=false&lt;/code> (или кратко &lt;code>-s=false&lt;/code>), и тогда вообще все примитивы и их массивы пойдут “под нож”.&lt;/p>
&lt;p>Такой режим не включен по умолчанию, вероятно, потому, что может усложнить дальнейший анализ. Разберём на примере. Допустим, нам нужно сопоставить данные дампа с метриками ОС или иной внешней телеметрией. Одним из главных “мостиков” для этого послужит PID процесса. И хотя его можно получить разными способами, удобнее всего было бы взять его напрямую из дампа – в случае Spring Boot для этого достаточно выполнить вот такой OQL-запрос:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">OBJECTS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">source&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">org&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">springframework&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">boot&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ApplicationInfoPropertySource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Для исходного (“сырого”) дампа он вернёт HashMap примерно с таким содержимым:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="image-20260409222429500" srcset="
/post/2026/hprof-redaction/img/image-20260409222429500_hu71a0ccbc17ecd3dfbbf88dcd6af84d28_14107_af5ffdbf7cd8f3d0b9601a89fcf292ab.webp 400w,
/post/2026/hprof-redaction/img/image-20260409222429500_hu71a0ccbc17ecd3dfbbf88dcd6af84d28_14107_8e1d789547ceb466dd0f09e819351646.webp 760w,
/post/2026/hprof-redaction/img/image-20260409222429500_hu71a0ccbc17ecd3dfbbf88dcd6af84d28_14107_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://toparvion.pro/post/2026/hprof-redaction/img/image-20260409222429500_hu71a0ccbc17ecd3dfbbf88dcd6af84d28_14107_af5ffdbf7cd8f3d0b9601a89fcf292ab.webp"
width="646"
height="150"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>Однако после обфускации этот результат, очевидно, изменится. И если &lt;code>spring.application.version&lt;/code> нам не спасти в любом случае (так как это строковое значение), то целостность &lt;code>spring.application.pid&lt;/code> целиком зависит от флага &lt;code>--sanitize-byte-char-arrays-only&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>при значении &lt;code>true&lt;/code> (по умолчанию) PID уцелеет&lt;/li>
&lt;li>а при &lt;code>false&lt;/code> содержимое мапы будет примерно таким:&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="image-20260409222944631" srcset="
/post/2026/hprof-redaction/img/image-20260409222944631_huc2556372b1f778840d9b2c94b21e8353_12141_402912c92278ceb2027d882acd51690f.webp 400w,
/post/2026/hprof-redaction/img/image-20260409222944631_huc2556372b1f778840d9b2c94b21e8353_12141_9de9c475d518b37b9851b8d520da24fa.webp 760w,
/post/2026/hprof-redaction/img/image-20260409222944631_huc2556372b1f778840d9b2c94b21e8353_12141_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://toparvion.pro/post/2026/hprof-redaction/img/image-20260409222944631_huc2556372b1f778840d9b2c94b21e8353_12141_402912c92278ceb2027d882acd51690f.webp"
width="760"
height="153"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>, т.е. максимально “безопасным”.&lt;/p>
&lt;p>Если бы PID был записан в отдельное поле какого-либо класса, его ещё можно было бы спасти опцией &lt;code> --exclude-string-fields&lt;/code> (кратко &lt;code>-e&lt;/code>), которая говорит обфускатору, какие поля каких классов нужно оставить нетронутыми. По умолчанию такими полями являются только имена потоков и их групп (см. фрагмент лога выше).&lt;/p>
&lt;p>У особо &lt;del>занудного&lt;/del> внимательного читателя здесь может возникнуть вопрос:&lt;/p>
&lt;blockquote>
&lt;p>Зачем указывать команду &lt;code>sanitize&lt;/code>, если инструмент и так создан для этого?&lt;/p>
&lt;/blockquote>
&lt;p>Дело в том, что он умеет не только обрабатывать готовые дампы, но и снимать их, причём снимать с приложений в контейнерах (в том числе когда сам запущен в контейнере). Для этого нужно сменить команду &lt;code>sanitize&lt;/code> на &lt;code>capture&lt;/code>, а путь к дампу – на имя целевого контейнера. В этом случае снятый дамп будет лежать внутри &lt;strong>целевого&lt;/strong> контейнера и его нужно будет забрать оттуда руками (если успеете, пока его не прибил какой-нибудь Kubernetes).&lt;/p>
&lt;p>Даже не вникая в остальные возможности этого инструмента, уже должно стать ясно, что он хорошо подходит для нашей задачи:&lt;/p>
&lt;ul>
&lt;li>вычищает пароли из дампа даже с умолчательными настройками&lt;/li>
&lt;li>позволяет влиять на степень “дезинфекции”&lt;/li>
&lt;li>не требует парсинга дампа&lt;/li>
&lt;li>поставляется одним JAR-файлом (сделан на Spring Boot + PicoCLI)&lt;/li>
&lt;li>хорошо встраивается в CI/CD за счёт CLI-интерфейса.&lt;/li>
&lt;/ul>
&lt;p>Но куда ж без минусов:&lt;/p>
&lt;ul>
&lt;li>На огромных дампах инструмент может потребовать приседаний: под капотом он &lt;a href="https://github.com/paypal/heap-dump-tool/blob/76a45d56540ecf39773675b8c028d4f9f662a1e4/src/main/java/com/paypal/heapdumptool/sanitizer/SanitizeStreamFactory.java#L34" target="_blank" rel="noopener">использует&lt;/a> обычный &lt;code>BufferedInputStream&lt;/code> с размером буфера 100 Мб. И если на небольших дампах этого вполне хватает, то по мере роста их размера можно оказаться перед неприятным выбором: либо дольше ждать при их чтении/записи, либо выделять сильно больше памяти под буфер.&lt;/li>
&lt;li>В особо &lt;del>упоротых&lt;/del> специфичных случаях может потребоваться кастомная логика обфускации, но heap-dump-tool не предоставляет возможностей по её модификации, а форкать его ради этого – затея сомнительная.&lt;/li>
&lt;/ul>
&lt;p>Едва ли много кто сталкивается с этими проблемами, но если вдруг, то вот вариант и на этот случай:&lt;/p>
&lt;h3 id="способ-3-hprof-redact">Способ 3. Hprof-redact&lt;/h3>
&lt;p>Широко известный в узких кругах JVM-спец &lt;a href="https://mostlynerdless.de/blog/author/parttimenerd/" target="_blank" rel="noopener">Johannes Bechberger&lt;/a> является автором множества полезных утилит для анализа и оптимизации производительности. И недавно &lt;a href="https://mostlynerdless.de/blog/2026/02/13/redacting-sensitive-data-from-java-flight-recorder-files/#comment-57251" target="_blank" rel="noopener">с подачи&lt;/a> другого известного в Java мире эксперта Volker’а Simonis’а он выпустил &lt;strong>&lt;a href="https://github.com/parttimenerd/hprof-redact" target="_blank" rel="noopener">hprof-redact&lt;/a>&lt;/strong> – инструмент для обфускации дампов памяти перед передачей на анализ.&lt;/p>
&lt;p>Инструмент во многом похож на описанный выше Heap Dump Tool:&lt;/p>
&lt;ul>
&lt;li>тоже CLI&lt;/li>
&lt;li>тоже поставляется одним JAR-архивом&lt;/li>
&lt;li>тоже предлагает разные режимы редактирования данных.&lt;/li>
&lt;/ul>
&lt;p>Но на этом основные сходства заканчиваются и начинаются различия:&lt;/p>
&lt;ul>
&lt;li>по &lt;a href="https://github.com/parttimenerd/hprof-redact?tab=readme-ov-file#:~:text=This%20is%20currently%20just%20an%20early%20prototype" target="_blank" rel="noopener">заявлению&lt;/a> автора, это не полноценный инструмент, а прототип (proof-of-concept)&lt;/li>
&lt;li>читает дампы в поточном режиме:
&lt;ul>
&lt;li>из плюсов: не требует тюнинга буфера для больших дампов&lt;/li>
&lt;li>из минусов: делает два полных прохода по дампу, поэтому работает всё равно не мгновенно;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>управляет редактированием через т.н. &lt;em>трансформеры&lt;/em>, коих из коробки в версии 0.2.1 (февраль 2026) было три:
&lt;ul>
&lt;li>&lt;strong>zero&lt;/strong> (умолчательный) – обнуляет все примитивы и заменяет содержимое строк нулями;&lt;/li>
&lt;li>&lt;strong>zero-strings&lt;/strong> – тоже заменяет содержимое строк нулями, но оставляет примитивы нетронутыми;&lt;/li>
&lt;li>&lt;strong>drop-strings&lt;/strong> – делает все строки пустыми (нулевой длины), но оставляет примитивы нетронутыми.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>Последний пункт, к слову, является главным дизайнерским отличием hprof-redact от heap-dump-tool, поскольку трансформер – это часть публичного API инструмента, что позволяет при необходимости написать собственную логику обфускации, имплементировав развесистый, но полностью опциональный интерфейс &lt;a href="https://github.com/parttimenerd/hprof-redact/blob/main/src/main/java/me/bechberger/hprof/transformer/HprofTransformer.java" target="_blank" rel="noopener">HprofTransformer&lt;/a>.&lt;/p>
&lt;p>Отдельно для hrpof-redact стоит упомянуть &lt;strong>сжатие дампа при обработке&lt;/strong>. Оно есть и в других инструментах, но Eclipse MAT и heap-dump-tool реализуют его обычным GZip-ом, а у hprof-redact &lt;a href="https://github.com/parttimenerd/hprof-redact?tab=readme-ov-file#compression-format" target="_blank" rel="noopener">иной подход&lt;/a> – он опустошает строки и примитивные массивы, сохраняя при этом информацию об их исходной длине. В этом смысле такое сжатие не совсем честно, ведь оно теряет данные без возможности восстановления. Однако и цель у него немного иная – оно призвано сократить объём дампа, сохранив его &lt;em>структурную&lt;/em>, но не содержательную идентичность.&lt;/p>
&lt;p>В отличие от heap-dump-tool, утилита hprof-redact по умолчанию вычищает не только строки, но и примитивы (включая их массивы), поэтому оба приведенных выше OQL-запроса (для выявления пароля и получения PID+версии приложения) вернут большое… ничего. Всё на радость отделу ИБ.&lt;/p>
&lt;p>В целом, hprof-redact пока сложно считать полноценным решением, поскольку он создан совсем недавно и пока что предоставляет не особо много возможностей. Однако он может стать достойной альтернативной тому же heap-dump-tool’у в случаях, когда требуется особая логика обфускации данных в дампе и вы готовы прописать её программно.&lt;/p>
&lt;h3 id="способ-4-jdk">&lt;del>Способ 4. JDK&lt;/del>&lt;/h3>
&lt;p>Вообще, прежде чем рассматривать какие-то сторонние инструменты, было бы логичнее поинтересоваться, а нет ли в самом JDK чего-то готового? Ответ: сейчас нет, но обсуждения ведутся.&lt;/p>
&lt;p>В JIRA проекта OpenJDK есть тикет &lt;a href="https://bugs.openjdk.org/browse/JDK-8337517" target="_blank" rel="noopener">JDK-8337517&lt;/a>, в котором предлагается доработать утилиту jcmd, а также добавить новую JVM-опцию &lt;code>-XX:+HeapDumpRedacted&lt;/code>, чтобы и та, и другая на выходе давали дезинфицированный дамп с занулёнными примитивами. Под него также есть соответствующий &lt;a href="https://github.com/openjdk/jdk/pull/20409" target="_blank" rel="noopener">Pull Request&lt;/a> на Github, однако он в статусе &lt;em>Closed&lt;/em>, а сам тикет хоть и имеет статус &lt;em>Open&lt;/em>, но поле &lt;em>Fix Version(s)&lt;/em> у него в &lt;code>tbd&lt;/code>, т.е. “когда-нибудь”.&lt;/p>
&lt;p>Основными причинами такого расклада стали невысокий приоритет задачи и отсутствие (пока) у разработчиков и ревьюеров единого взгляда на то, как должна быть имплементирована эта фича: что именно нужно очищать, какими должны быть аргументы вызова и как быть с тем, что если JVM выдаст обфусцированный дамп, то взять оригинальный будет уже негде. В этом обсуждении участвует и небезызвестный нам Алексей Шипилёв, &lt;a href="https://bugs.openjdk.org/browse/JDK-8337517?focusedId=14695155&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14695155" target="_blank" rel="noopener">комментируя&lt;/a> в свойственной себе манере:&lt;/p>
&lt;blockquote>
&lt;p>… while most of the time the confidential data is in primitive arrays (key material, cipher buffers, string contents), primitive fields carry identifiable data as well, e.g. numeric account/transaction IDs. Even double/floats contain data often, think financial data or even (pants heavily) LLM weights.&lt;/p>
&lt;/blockquote>
&lt;p>hprof-redact является попыткой имплементировать эту фичу по исходной (авторской) задумке. Насколько (не)удачной она получилась – судить, в том числе, нам с вами, её пользователям, поэтому если у вас есть опыт её использования и соображения на его счёт, не стесняйтесь делиться ими, например, на &lt;a href="https://mostlynerdless.de/blog/2026/02/24/redacting-data-from-heap-dumps-via-hprof-redact/" target="_blank" rel="noopener">странице&lt;/a> разработчика или через &lt;a href="https://github.com/parttimenerd/hprof-redact/issues" target="_blank" rel="noopener">GitHub issues&lt;/a>.&lt;/p>
&lt;h3 id="заключение">Заключение&lt;/h3>
&lt;p>Для дезинфекции дампов перед их анализом существует не так уж много бесплатных инструментов. А основных среди них и вовсе только два:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://eclipse.dev/mat/" target="_blank" rel="noopener">&lt;strong>Eclipse MAT&lt;/strong>&lt;/a> – мощный, но тяжелый; требует многих приседаний, но предоставляет максимальную свободу действий (GUI / CLI / API);&lt;/li>
&lt;li>&lt;a href="https://github.com/paypal/heap-dump-tool" target="_blank" rel="noopener">&lt;strong>heap-dump-tool&lt;/strong>&lt;/a> – не богатый, зато лёгкий; отлично встраивается в pipeline’ы; умеет не всё, но многое (CLI / API).&lt;/li>
&lt;/ul>
&lt;p>Особнячком от них стоит совсем новый инструмент &lt;a href="https://github.com/parttimenerd/hprof-redact" target="_blank" rel="noopener">hprof-redact&lt;/a>, во многом похожий на heap-dump-tool, но делающий ставку на гибкость Java API и поддержку больших дампов.&lt;/p>
&lt;p>Чуть более формальное сравнение этих инструментов, во многом основанное на субъективных оценках автора, приведено в этой таблице:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Фича\инструмент&lt;/th>
&lt;th>Eclipse MAT&lt;/th>
&lt;th>Heap Dump Tool&lt;/th>
&lt;th>Hprof-redact&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Работа с большими дампами&lt;/td>
&lt;td>👍&lt;/td>
&lt;td>👍👍&lt;/td>
&lt;td>👍👍👍&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Поддержка Docker контейнеров&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>❌&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Гибкость настроек обфускации&lt;/td>
&lt;td>👍👍👍&lt;/td>
&lt;td>👍&lt;/td>
&lt;td>👍👍&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Сжатие при обфускации&lt;/td>
&lt;td>✅ (zip)&lt;/td>
&lt;td>✅ (zip)&lt;/td>
&lt;td>✅ (hprof)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Гибкость API&lt;/td>
&lt;td>👍👍👍&lt;/td>
&lt;td>👍&lt;/td>
&lt;td>👍👍&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Загрузка дампов в формате TAR&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>❌&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Пригодность для CI/CD&lt;/td>
&lt;td>👍&lt;/td>
&lt;td>👍👍&lt;/td>
&lt;td>👍👍&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Если вам доводилось решать такую задачу с помощью этих или иных инструментов, расскажите, пожалуйста, об этом в комментариях – ваш опыт станет отличным дополнением к этой статье.&lt;/p></description></item></channel></rss>