Dalibo випустив PostgreSQL Anonymizer 3.3 – і це не плановий мінорний реліз із парою покращень, а закриття 12 критичних вразливостей. Частина з них дозволяла masked-користувачам – тобто саме тим, кому доступ до реальних даних мав бути закритий – читати оригінальні, немасковані значення. Інші відкривали шлях до підвищення привілеїв. Якщо у твоєму стеку є цей extension, апгрейд варто планувати не «колись у цьому спринті», а зараз.
Навіщо взагалі комусь маскувати дані в PostgreSQL
PostgreSQL Anonymizer – це extension, який приховує або замінює персональні чи комерційно чутливі дані безпосередньо в базі. За даними PostgreSQL News, він пропонує шість стратегій: dynamic masking (маскування в реальному часі), static masking (постійна трансформація даних), replica masking (анонімізована логічна реплікація), backup masking (експорти без PII), masking views і masking data wrappers для систем за межами однієї бази.
Типовий сценарій – команда хоче дати QA або аналітикам доступ до копії продакшн-бази, але без реальних імен, email і платіжних даних. Замість того щоб писати власний ETL-скрипт, який генерує фейкові дані, extension дозволяє описати правило маскування прямо в базі: роль бачить не NULL і не помилку, а правдоподібне, але вигадане значення.
Проблема в тому, що «правдоподібне, але вигадане» – це контракт довіри. Якщо masked-роль у якийсь спосіб бачить справжнє значення хоч іноді, весь сенс інструмента зникає. Саме це і сталось у версіях до 3.3.
Що саме ламалось: три категорії проблем
Якщо подивитись на перелік із 12 CVE, вони групуються не хаотично, а довкола трьох типових для security-систем помилок.
Перша категорія – обхід маскування через нетривіальні шляхи виконання SQL. CVE-2026-110181 дозволяв читати автентичні дані через SQL-функції з SQL-standard body – включно з функціями з contrib-розширень на кшталт citext чи earthdistance. CVE-2026-105617 показував, що кешовані execution plans можуть обходити transparent dynamic masking: план побудований один раз без маски, і наступні виклики її вже не застосовують. CVE-2026-105620 і CVE-2026-105612 стосувались підзапитів і data-modifying CTE, які обходили read-only обмеження. Логіка однакова: маскування перевіряється на «головному» рівні запиту, але PostgreSQL дає достатньо способів дістатись до даних манівцями.
Друга категорія – метадані, які самі по собі є витоком. CVE-2026-105615 дозволяв masked-ролям читати pg_stats – системну статистику, яка в PostgreSQL часто містить приклади реальних значень колонки для оцінки планувальника. CVE-2026-110183 йшов ще далі: навіть після static masking, pg_stats продовжував зберігати автентичні значення замаскованих колонок. Тобто дані в таблиці вже замасковані, а статистика про них – ще немає. І CVE-2026-105616 – секретна сіль (salt), яка використовується для детермінованої псевдонімізації, читалась прямо з pg_db_role_setting. Якщо сіль відома, частину pseudonymization-функцій можна обернути назад.
Третя категорія – самі маскувальні функції. CVE-2026-110182 стосувався anon.partial() і anon.partial_email() – функцій часткового маскування, які саме й могли призводити до витоку даних. Є і DoS-вектор: CVE-2026-105614 та CVE-2026-105613 про необмежене виділення пам’яті в image-функціях і DoS у range-based dummy-функціях.
У відповідь на це 3.3 вводить нові обмеження, а не лише патчить окремі функції: subqueries в masking rules тепер заборонені, inlining функцій вимкнено для transparent dynamic masking, і masked-ролям заборонено виконувати функції з SQL-standard body взагалі – включно з contrib-розширеннями. Це архітектурний зсув: замість «залатати конкретну дірку» команда звузила поверхню атаки на рівні дозволених конструкцій.
Як виглядає правило маскування на практиці
Концептуально robота з extension будується навколо security labels – PostgreSQL-механізму, який дозволяє навісити метадані на об’єкт бази. Схематично (синтаксис ілюстративний, перевіряй актуальну документацію перед використанням) ідея виглядає так:
-- умовний приклад: правило часткового маскування email
SECURITY LABEL FOR anon ON COLUMN customers.email
IS 'MASKED WITH FUNCTION anon.partial_email()';Важливо розуміти: саме anon.partial_email() – одна з функцій, яку патчили в CVE-2026-110182. Якщо твоя команда використовувала часткове маскування email чи інших полів через функції цього типу, недостатньо просто оновити extension – варто перевірити, чи не потрапляли замасковані значення кудись у логи, кеш застосунку чи аналітичні dashboard-и до апгрейду. Маскування на рівні бази не захищає дані, які вже встигли «просочитись» через застосунок раніше.
Де маскування на рівні бази – не панацея
Чесно кажучи, цей реліз – хороший привід тверезо подивитись на весь підхід. Маскування в базі даних – це рівень захисту, а не повна заміна access control. Сама природа знайдених CVE показує: будь-яка система, що намагається «перехопити» дані на шляху до користувача (plan caching, views, CTE, функції), має величезну площу для обходу. Це та сама проблема, що й у будь-якому runtime security layer – принцип найменших привілеїв все одно має бути першою лінією оборони, а не остання.
Якщо тобі цікаво, як це питання вирішується на рівні процесу, а не бази даних, подивись Node.js Permission Model – там розбирається та сама ідея обмеження доступу, тільки для fs, child_process і мережі.
Інше обмеження – операційне. Зміна anon.salt, яку Dalibo рекомендує зробити після оновлення, означає, що всі значення, псевдонімізовані зі старою сіллю, більше не будуть консистентними з новими. Якщо твоя система покладалась на те, що одне й те саме ім’я завжди маскується в одне й те саме псевдо-значення (а це типова вимога для тестових даних, де потрібна відтворюваність), планування цієї зміни – окрема задача, яку варто зробити до продакшн-апгрейду, а не після.
Вибір PostgreSQL як бази для систем з великим обсягом PII – окрема розмова; якщо вона для тебе актуальна, в SQL vs NoSQL є аргументи, коли реляційна модель виграє саме завдяки таким розширюваним механізмам контролю доступу.
Коли цей апдейт брати в роботу негайно
Якщо PostgreSQL Anonymizer стоїть у тебе в staging, demo-середовищах чи для анонімізованої логічної реплікації – оновлення до 3.3 не чекає наступного рефакторингу. Особливо критично, якщо:
- masked-ролі видаються зовнішнім підрядникам, QA-аутсорсу чи сторонній аналітиці – саме цей сценарій ламали CVE на обхід маскування через pg_stats і subqueries;
- використовується static masking для «постійного» очищення копій продакшн-бази – pg_stats міг зберігати оригінальні значення навіть після трансформації;
- у pipeline є кешування планів запитів або connection pooling з довгоживучими з’єднаннями – саме там ховався CVE-2026-105617.
Якщо extension використовується лише для backup masking разових експортів без постійних masked-ролей, тиск менший, але оновлення все одно варте того, щоб не лишати відому вразливість у системі, яка торкається PII.
Підсумок без ілюзій
Маскування даних – це не галочка в compliance-чеклисті, а окрема підсистема з власною поверхнею атаки, яку треба патчити й ревізувати так само, як будь-який auth-механізм. Цей реліз – хороший привід не просто оновити версію пакета, а перевірити: хто саме має masked-ролі у твоїй базі, чи логуються замасковані значення десь поза контролем extension, і чи готова команда періодично переглядати ці дозволи так само, як переглядає API keys чи service accounts.