#3194: ключ шифрования кук и сами куки больше не уезжают в GlitchTip #3275
7 changed files with 101 additions and 3 deletions
|
|
@ -43,6 +43,12 @@ def run_migrations_online() -> None:
|
|||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
# #3194: SQLAlchemy печатает ВСЕ bind-параметры в тексте StatementError.
|
||||
# Миграции гоняют DDL/DML с литералами и параметрами из данных — флаг
|
||||
# на уровне движка не даёт им уехать в GlitchTip.
|
||||
# НЕ закрывает: текст ошибки самого драйвера (Postgres DETAIL со
|
||||
# значением) и сырые psycopg-подключения мимо движков.
|
||||
hide_parameters=True,
|
||||
)
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
|
|
|
|||
|
|
@ -139,6 +139,13 @@ def _build() -> tuple[Engine, sessionmaker[Session]]:
|
|||
future=True,
|
||||
pool_timeout=3,
|
||||
connect_args={"connect_timeout": 3, "options": "-c statement_timeout=3000"},
|
||||
# #3194: SQLAlchemy печатает ВСЕ bind-параметры в тексте StatementError —
|
||||
# через них в GlitchTip уезжали ключ шифрования кук и сами куки
|
||||
# (pgp_sym_encrypt(:cookies_json, :key)). Флаг на УРОВНЕ ДВИЖКА кроет все
|
||||
# сайты вызова разом, включая будущие.
|
||||
# НЕ закрывает: текст ошибки самого драйвера (Postgres DETAIL со значением)
|
||||
# и сырые psycopg-подключения мимо движков — это отдельный класс.
|
||||
hide_parameters=True,
|
||||
)
|
||||
except (ArgumentError, ValueError):
|
||||
# ValueError — не паранойя: на «почти URL» разбор SQLAlchemy доходит до
|
||||
|
|
|
|||
|
|
@ -5,7 +5,18 @@ from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
|||
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_engine(settings.database_url, pool_pre_ping=True, future=True)
|
||||
engine = create_engine(
|
||||
settings.database_url,
|
||||
pool_pre_ping=True,
|
||||
future=True,
|
||||
# #3194: SQLAlchemy печатает ВСЕ bind-параметры в тексте StatementError —
|
||||
# через них в GlitchTip уезжали ключ шифрования кук и сами куки
|
||||
# (pgp_sym_encrypt(:cookies_json, :key)). Флаг на УРОВНЕ ДВИЖКА кроет все
|
||||
# сайты вызова разом, включая будущие.
|
||||
# НЕ закрывает: текст ошибки самого драйвера (Postgres DETAIL со значением)
|
||||
# и сырые psycopg-подключения мимо движков — это отдельный класс.
|
||||
hide_parameters=True,
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=False)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,18 @@ def _build() -> tuple[Engine, sessionmaker[Session]]:
|
|||
if not dsn:
|
||||
raise AuthDatabaseNotConfiguredError(_NOT_CONFIGURED_MSG)
|
||||
try:
|
||||
engine = create_engine(dsn, pool_pre_ping=True, future=True)
|
||||
engine = create_engine(
|
||||
dsn,
|
||||
pool_pre_ping=True,
|
||||
future=True,
|
||||
# #3194: SQLAlchemy печатает ВСЕ bind-параметры в тексте StatementError —
|
||||
# через них в GlitchTip уезжали ключ шифрования кук и сами куки
|
||||
# (pgp_sym_encrypt(:cookies_json, :key)). Флаг на УРОВНЕ ДВИЖКА кроет все
|
||||
# сайты вызова разом, включая будущие.
|
||||
# НЕ закрывает: текст ошибки самого драйвера (Postgres DETAIL со значением)
|
||||
# и сырые psycopg-подключения мимо движков — это отдельный класс.
|
||||
hide_parameters=True,
|
||||
)
|
||||
except (ArgumentError, ValueError):
|
||||
# ValueError — не паранойя: на «почти URL» разбор SQLAlchemy доходит до
|
||||
# `int(port)` и падает с `invalid literal for int() with base 10: 'w'`,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,18 @@ from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
|||
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_engine(settings.database_url, pool_pre_ping=True, future=True)
|
||||
engine = create_engine(
|
||||
settings.database_url,
|
||||
pool_pre_ping=True,
|
||||
future=True,
|
||||
# #3194: SQLAlchemy печатает ВСЕ bind-параметры в тексте StatementError —
|
||||
# через них в GlitchTip уезжали ключ шифрования кук и сами куки
|
||||
# (pgp_sym_encrypt(:cookies_json, :key)). Флаг на УРОВНЕ ДВИЖКА кроет все
|
||||
# сайты вызова разом, включая будущие.
|
||||
# НЕ закрывает: текст ошибки самого драйвера (Postgres DETAIL со значением)
|
||||
# и сырые psycopg-подключения мимо движков — это отдельный класс.
|
||||
hide_parameters=True,
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=False)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,11 @@ if settings.glitchtip_dsn:
|
|||
release=os.getenv("GIT_SHA") or os.getenv("SENTRY_RELEASE") or "unknown",
|
||||
traces_sample_rate=0.0,
|
||||
send_default_pii=False,
|
||||
# #3194: процесс скрейпера — в локальных переменных кадров лежат
|
||||
# прокси-креды вида user:pass, default sentry_sdk (True) приложил бы их
|
||||
# к traceback открытым текстом. Так же выставлено у соседей (main.py,
|
||||
# tgbot). НЕ закрывает значения в самом тексте исключения и в логах.
|
||||
include_local_variables=False,
|
||||
before_send=_before_send,
|
||||
before_send_transaction=_before_send,
|
||||
integrations=[
|
||||
|
|
|
|||
47
tradein-mvp/backend/tests/test_3194_hide_sql_parameters.py
Normal file
47
tradein-mvp/backend/tests/test_3194_hide_sql_parameters.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"""#3194: bind-параметры не должны попадать в текст исключения (→ GlitchTip).
|
||||
|
||||
Через них уезжали ключ шифрования кук и сами куки: сервисы сессий зовут
|
||||
`db.execute(text("... pgp_sym_encrypt(:cookies_json, :key) ..."), {...})`, а
|
||||
SQLAlchemy при ошибке оборачивает всё в StatementError и печатает ВСЕ
|
||||
параметры в `str(exc)`.
|
||||
|
||||
Отказ здесь обязан произойти НА УРОВНЕ ЗАПРОСА: если соединение падает на
|
||||
CONNECT, до bind-параметров дело не доходит и тест зелёный по построению.
|
||||
Поэтому — живой sqlite в памяти и обращение к несуществующей таблице.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.exc import StatementError
|
||||
|
||||
SECRET = "sup3r-secret-cookie-key-3194"
|
||||
|
||||
|
||||
def _provoke_statement_error(*, hide: bool) -> str:
|
||||
engine = create_engine("sqlite://", hide_parameters=hide)
|
||||
with engine.connect() as conn:
|
||||
with pytest.raises(StatementError) as excinfo:
|
||||
conn.execute(text("SELECT :key FROM no_such_table_3194"), {"key": SECRET})
|
||||
return str(excinfo.value)
|
||||
|
||||
|
||||
def test_bind_parameters_hidden_in_exception_text() -> None:
|
||||
message = _provoke_statement_error(hide=True)
|
||||
# Отказ именно на запросе, а не на соединении — иначе проверка ниже пуста.
|
||||
assert "no_such_table_3194" in message
|
||||
assert SECRET not in message
|
||||
|
||||
|
||||
def test_falsification_secret_leaks_without_the_flag() -> None:
|
||||
"""Обратная сторона: без флага секрет ВИДЕН — тест выше не тавтология."""
|
||||
message = _provoke_statement_error(hide=False)
|
||||
assert SECRET in message
|
||||
|
||||
|
||||
def test_app_engine_hides_parameters() -> None:
|
||||
"""Сам прод-движок, а не только принцип: create_engine ленив, коннекта нет."""
|
||||
from app.core.db import engine
|
||||
|
||||
assert engine.hide_parameters is True
|
||||
Loading…
Add table
Reference in a new issue