Разбирал форки стартера и наткнулся на класс дыр, от которого методология сейчас не страхует. Кладу сюда с разбором — фикс уже готов отдельным PR.
Суть
Claude, когда пишет код, по умолчанию любит «удобные» fallback-значения для секретов, чтобы всё сразу запускалось локально:
const secret = process.env.JWT_SECRET ?? 'dev-secret'; // подпись токенов авторизации
const key = Buffer.concat([Buffer.from(process.env.ENCRYPTION_KEY ?? ''), Buffer.alloc(32)]).subarray(0, 32); // пустой ключ → предсказуемые нули
// docker-compose: MYSQL_PASSWORD: ${DB_PASS:-apppass}
Локально удобно. Но если задеплоить, забыв выставить переменную окружения, приложение не падает — оно молча стартует с секретом, который лежит открытым текстом в публичном репозитории. А 'dev-secret' видит любой, кто открыл репо.
Чем это грозит
JWT_SECRET — это чем подписываются токены сессий. Знаешь секрет → подделываешь любой токен и входишь под кем угодно без пароля. Полный обход авторизации. То же с ключом шифрования: предсказуемый ключ = данные, которые ты «зашифровал», расшифровываются кем угодно. Пароль БД по умолчанию — открытая дверь в базу, если порт где-то торчит.
Коварство в том, что всё выглядит рабочим: приложение поднялось, логин проходит, тесты зелёные. Дыра невидима, пока её не поюзают.
Это не гипотетика
Ровно этот footgun воспроизвёлся в одном из форков стартера — реальный человек прошёл онбординг, построил продукт, и в публичный форк уехали и JWT_SECRET ?? 'dev-secret' (web/src/lib/auth.ts), и ключ шифрования с добивкой нулями (web/src/lib/crypto.ts), и дефолтные пароли БД. Человек не виноват — Claude так написал, а правила «так нельзя» в стартере не было.
Аудитория стартера — вайб-кодеры без security-бэкграунда. Они получат это по умолчанию и не заметят.
Фикс
Дешёвый — одно правило в раздел «Безопасность» CLAUDE.md: у секретов не должно быть fallback; нет обязательной env-переменной → падай при старте (throw), а не подставляй заглушку. Тогда «забыл переменную на проде» превращается из тихой дыры в громкую ошибку запуска, которую невозможно не заметить.
Готовый PR: #4
Если формулировка не по тону — перепиши, суть в том, чтобы правило вообще появилось.
Разбирал форки стартера и наткнулся на класс дыр, от которого методология сейчас не страхует. Кладу сюда с разбором — фикс уже готов отдельным PR.
Суть
Claude, когда пишет код, по умолчанию любит «удобные» fallback-значения для секретов, чтобы всё сразу запускалось локально:
Локально удобно. Но если задеплоить, забыв выставить переменную окружения, приложение не падает — оно молча стартует с секретом, который лежит открытым текстом в публичном репозитории. А
'dev-secret'видит любой, кто открыл репо.Чем это грозит
JWT_SECRET— это чем подписываются токены сессий. Знаешь секрет → подделываешь любой токен и входишь под кем угодно без пароля. Полный обход авторизации. То же с ключом шифрования: предсказуемый ключ = данные, которые ты «зашифровал», расшифровываются кем угодно. Пароль БД по умолчанию — открытая дверь в базу, если порт где-то торчит.Коварство в том, что всё выглядит рабочим: приложение поднялось, логин проходит, тесты зелёные. Дыра невидима, пока её не поюзают.
Это не гипотетика
Ровно этот footgun воспроизвёлся в одном из форков стартера — реальный человек прошёл онбординг, построил продукт, и в публичный форк уехали и
JWT_SECRET ?? 'dev-secret'(web/src/lib/auth.ts), и ключ шифрования с добивкой нулями (web/src/lib/crypto.ts), и дефолтные пароли БД. Человек не виноват — Claude так написал, а правила «так нельзя» в стартере не было.Аудитория стартера — вайб-кодеры без security-бэкграунда. Они получат это по умолчанию и не заметят.
Фикс
Дешёвый — одно правило в раздел «Безопасность»
CLAUDE.md: у секретов не должно быть fallback; нет обязательной env-переменной → падай при старте (throw), а не подставляй заглушку. Тогда «забыл переменную на проде» превращается из тихой дыры в громкую ошибку запуска, которую невозможно не заметить.Готовый PR: #4
Если формулировка не по тону — перепиши, суть в том, чтобы правило вообще появилось.