Секретный ключ, который лежал в коде
Запасное значение в os.environ.get выглядит удобно, пока не окажется, что боевой сервер работает именно на нём.
В приложении на Flask была строчка, которая выглядела абсолютно нормально:
app.secret_key = os.environ.get('SECRET_KEY', 'Softropix-2026-key-final')
Читается она так: возьми ключ из переменных окружения, а если его там нет — используй вот этот. Удобно. Приложение запустится где угодно, ничего дополнительно настраивать не надо.
Именно в этом удобстве и проблема.
Что делает этот ключ
Flask подписывает им сессии. Когда вы сохраняете что-то в session, данные уходят в cookie посетителя, а рядом кладётся подпись, сделанная этим ключом. При следующем запросе Flask проверяет подпись и понимает, что содержимое не подделали.
Пока ключ известен только серверу, схема работает. Если ключ знает кто-то ещё — он может собрать cookie с любым содержимым и подписать её так, что сервер поверит.
У меня в сессии хранился всего лишь выбранный язык интерфейса. Красть нечего. Но стоило появиться входу в аккаунт — и любой человек с доступом к репозиторию мог бы войти под чужим именем.
Настоящая проблема — не ключ
Строку с ключом можно поменять за минуту. Хуже другое: запасное значение скрывает от вас правду.
Приложение работает. Логи чистые. Сайт открывается. И вы искренне уверены, что переменная окружения на сервере настроена — иначе как бы оно работало.
А работало оно на запасном ключе. Всё это время.
Значение по умолчанию превращает вопрос «настроено ли окружение» в вопрос, который вам никогда не зададут.
Как я это выяснил
Правка простая — убрать запасной вариант:
app.secret_key = os.environ['SECRET_KEY']
Квадратные скобки вместо get. Нет переменной — приложение падает сразу при старте, с внятной ошибкой.
Я добавил переменную в панель хостинга, отправил правку, дождался сборки и открыл логи:
KeyError: 'SECRET_KEY'
[ERROR] Worker (pid:6) exited with code 3
[ERROR] Reason: Worker failed to boot.
Сайт лёг. И это был лучший исход из возможных.
Падение означало ровно одно: переменной на сервере действительно не было. То есть до этого момента боевое приложение работало на ключе, зашитом в исходники и лежащем в открытом репозитории.
Выяснилось это только потому, что я убрал подстраховку. С прежней строкой я бы не узнал никогда.
Почему переменная не подхватилась
Оказалось, я добавил её в диалоге панели, но не сохранил саму форму настроек. Классическая невнимательность. Вернулся, добавил заново, сохранил, запустил пересборку:
[INFO] Starting gunicorn 23.0.0
[INFO] Listening at: http://0.0.0.0:8000
[INFO] Booting worker with pid: 13
Ни одной ошибки. Теперь приложение точно работает на том ключе, который я задал.
Как запускать локально
После правки локальный запуск тоже перестал работать — на моём компьютере переменной нет. Это ожидаемо, но неудобно.
Решается пакетом python-dotenv . Кладёте в корень проекта файл .env :
SECRET_KEY=test123
И две строки в начало приложения:
from dotenv import load_dotenv
load_dotenv()
Важно: в .env пишется тестовое значение, а не боевой ключ. Настоящий живёт только в панели хостинга. И сам файл обязательно добавляется в .gitignore, иначе смысла в переезде не было.
Что делать со старым ключом
Важная деталь, о которой легко забыть: ключ остался в истории репозитория.
Вы поменяли строку, отправили изменения — в текущей версии кода ключа нет. Но любой, кто откроет прошлые коммиты, прочитает его без труда.
Поэтому старый ключ считается скомпрометированным навсегда. Не переносите его в переменные окружения — сгенерируйте новый:
python3 -c "import secrets; print(secrets.token_hex(32))"
Шестьдесят четыре символа случайных данных. Скопируйте в панель хостинга и сохраните у себя в менеджере паролей — иначе придётся генерировать заново, как это вышло у меня.
Правило, которое отсюда следует
Значения по умолчанию хороши для настроек, которые ничего не решают: размер страницы, язык интерфейса, таймаут. Там подстраховка уместна.
Для всего, что связано с безопасностью, работает обратное правило: пусть падает. Громкая ошибка при запуске лучше тихой работы на неправильных данных. Первое вы почините за десять минут. Второе может годами оставаться незамеченным.
Если приложение может запуститься без настройки, которая обязательна, — значит, эта настройка не обязательна. Проверьте, точно ли вы этого хотели.