# PostgreSQL Anonymizer 3.3: критичний security-реліз для масок, які виявились дірявими

> Dalibo випустив PostgreSQL Anonymizer 3.3 із фіксами 12 критичних CVE. Розбираємо, що саме ламало маскування PII і чому апгрейд не можна відкладати.

- Автор: Юра Скиба (https://cookiesoftware.io)
- Опубліковано: 2026-10-30
- Категорія: Backend і System Design
- Canonical: https://cookiesoftware.io/blog/postgresql-anonymizer-3-3-security-update

---
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-механізму, який дозволяє навісити метадані на об’єкт бази. Схематично (синтаксис ілюстративний, перевіряй актуальну документацію перед використанням) ідея виглядає так:

```sql
-- умовний приклад: правило часткового маскування 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](/blog/nodejs-permission-model) – там розбирається та сама ідея обмеження доступу, тільки для fs, child_process і мережі.

Інше обмеження – операційне. Зміна anon.salt, яку Dalibo рекомендує зробити після оновлення, означає, що всі значення, псевдонімізовані зі старою сіллю, більше не будуть консистентними з новими. Якщо твоя система покладалась на те, що одне й те саме ім’я завжди маскується в одне й те саме псевдо-значення (а це типова вимога для тестових даних, де потрібна відтворюваність), планування цієї зміни – окрема задача, яку варто зробити до продакшн-апгрейду, а не після.

Вибір PostgreSQL як бази для систем з великим обсягом PII – окрема розмова; якщо вона для тебе актуальна, в [SQL vs NoSQL](/blog/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.
