All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m44s
#3195 замержен с утверждением, что авторизованная сессия раскрывает контакты продавца. Утверждение неверно, а лежит оно в двух местах, которые читают в первую очередь: докстринг app/services/yandex_session.py и шапка миграции 274. Повторный замер (#3192, 2026-08-28) сделан на ПРОД-транспорте — curl_cffi + прокси из пула, тот же путь, что у yandex_detail_backfill, — а не на сайдкаре, как первый: - offerCard.card.author у целевой карточки не несёт phones/phoneNumbers ни в одном из 12 случайных объявлений (6 AGENCY, 6 DEVELOPER), одинаково с куками и без; только encryptedPhones (1 токен) и redirectPhones; - phoneNumbers во всём INITIAL_STATE встречается только под offerCard.visitedOffers[*].author — истории просмотров НАШЕЙ учётки; анонимно список пуст, с куками в нём 9-10 записей; - первый замер («0 → 3,4,5,6») считал рост именно этой истории: +1 на каждый фетч; - authorStats.phones (коммутатор застройщика) отдаётся анонимно — тот же номер в обеих ветках. Правка только текстовая: ни схема, ни поведение не меняются. Шапку применённой миграции правлю сознательно — файл повторно не выполняется (учёт по имени в _schema_migrations), а неверное описание пережило бы любой следующий разбор. Таблицу не трогаю: она пуста, но DROP без явного решения владельца делать нельзя. Судьба #3195 — на владельце, #3192 помечен needs-human. Refs #3192, #3195
63 lines
4.1 KiB
PL/PgSQL
63 lines
4.1 KiB
PL/PgSQL
-- 274_yandex_session_cookies.sql
|
||
-- Purpose: Encrypted storage for Yandex.Nedvizhimost browser session cookies.
|
||
-- Uses pgcrypto pgp_sym_encrypt for AES encryption at rest (зеркалит
|
||
-- 027_cian_session_cookies.sql / 174_domclick_session_cookies.sql).
|
||
--
|
||
-- ⚠️ ОБОСНОВАНИЕ ЭТОЙ ТАБЛИЦЫ ОПРОВЕРГНУТО (#3192, замер 2026-08-28).
|
||
-- Шапка этого файла утверждала, что авторизованная сессия раскрывает
|
||
-- author.phoneNumbers. Повторный замер на прод-транспорте (curl_cffi +
|
||
-- прокси, а не сайдкар) показал: у целевой карточки
|
||
-- (offerCard.card.author) телефонов нет никогда — 12 из 12 объявлений,
|
||
-- с куками и без, только encryptedPhones + redirectPhones.
|
||
-- phoneNumbers встречается ТОЛЬКО под offerCard.visitedOffers[*].author
|
||
-- — это история просмотров НАШЕЙ учётки, растущая на +1 с каждым
|
||
-- фетчем; именно её рост первый замер принял за данные объявления.
|
||
-- Таблица создана и пуста. Оставлена как есть: DROP без явного решения
|
||
-- владельца не делаем (#3192, label needs-human). Разбор — в тикете и
|
||
-- в докстринге app/services/yandex_session.py.
|
||
-- Dependencies:
|
||
-- - pgcrypto extension (installed here via CREATE EXTENSION IF NOT EXISTS)
|
||
-- Deploy order: Apply after 273.
|
||
--
|
||
-- Security notes:
|
||
-- - cookies_encrypted stores AES-encrypted JSON blob via pgp_sym_encrypt.
|
||
-- - Encryption key lives in .env.runtime (COOKIE_ENCRYPTION_KEY), never in DB.
|
||
-- - Access restricted to admin-token-gated API endpoint only.
|
||
--
|
||
-- Schema note: ключ записи — account_label (text), НЕ числовой id как у Cian
|
||
-- (account_user_id) / DomClick (account_cas_id) — у Яндекса нет внутреннего
|
||
-- числового id аккаунта, доступного из cookie-дампа; используем произвольную
|
||
-- текстовую метку аккаунта, которую задаёт оператор при заливке дампа.
|
||
--
|
||
-- Sources: issue #3192 (Yandex session cookies — author.phoneNumbers reveal)
|
||
|
||
BEGIN;
|
||
|
||
-- #2752: без lock_timeout CREATE INDEX встанет в очередь за чужой долгой
|
||
-- сессией и уведёт за собой запросы приложения — таблица новая и пустая, но
|
||
-- очередь блокировок этого не знает. Гейт check-migration-lock-timeout.py
|
||
-- держит правило на каждом PR.
|
||
SET LOCAL lock_timeout = '5s';
|
||
|
||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||
|
||
CREATE TABLE IF NOT EXISTS yandex_session_cookies (
|
||
account_label text PRIMARY KEY, -- произвольная метка аккаунта (нет числового id)
|
||
cookies_encrypted bytea NOT NULL, -- pgp_sym_encrypt(json_cookies, key)
|
||
expires_at_estimate timestamptz NOT NULL, -- estimated expiry (~30 days from upload)
|
||
uploaded_at timestamptz NOT NULL DEFAULT NOW(),
|
||
last_used_at timestamptz, -- set on each successful authenticated fetch
|
||
last_invalid_at timestamptz, -- set when session no longer authenticated
|
||
notes text -- e.g. 'Account: <label / email>'
|
||
);
|
||
|
||
-- Most recent upload first (admin dashboard ordering)
|
||
CREATE INDEX IF NOT EXISTS yandex_cookies_uploaded_idx
|
||
ON yandex_session_cookies (uploaded_at DESC);
|
||
|
||
-- Active session lookup (non-expired and not invalidated)
|
||
CREATE INDEX IF NOT EXISTS yandex_cookies_active_idx
|
||
ON yandex_session_cookies (expires_at_estimate)
|
||
WHERE last_invalid_at IS NULL;
|
||
|
||
COMMIT;
|