Рано или поздно любой браузерной IDE задают один и тот же вопрос: где хранить ключи? Проекту нужен endpoint API, панели ИИ — ключ провайдера, а деплою — токен аккаунта. На десктопе эти значения можно держать в профиле оболочки или в неотслеживаемом файле. В браузерной вкладке граница другая. Всё, что отправляется вместе со страницей, может быть прочитано страницей, а всё, что хранится в профиле браузера, принадлежит этому профилю. Это руководство предлагает практическую модель: какие значения можно публиковать, какие никогда не должны попадать к клиенту, как GB Coder обращается с учётными данными провайдеров и что делать при утечке ключа. Оно для тех, кто хочет удобство браузерной IDE, не делая вид, что вкладка — это секретный сейф.
1. Браузерная вкладка — публичная цель сборки
Первое правило неудобно, но просто: статическое веб-приложение не может хранить секрет. Браузер скачивает HTML, CSS и JavaScript, а затем выполняет их на машине пользователя. Любой может открыть DevTools, посмотреть сетевую панель, поискать по бандлу или поставить точку останова и проверить переменную. Минификация, разбиение на чанки или сокрытие значения в конфигурационном файле меняют затраты, но не результат. Браузерная IDE добавляет песочницу для выполнения кода проекта, но интерфейс самой IDE по-прежнему работает на том же origin и должен читать то, что ему нужно для работы. Песочница не является сейфом для значений, которые использует сам интерфейс. Полезный тест прост: если значение должно быть доступно коду во вкладке, считайте, что человек, использующий вкладку, тоже может его прочитать, как и любой скрипт на том же origin. Это не делает браузерную разработку небезопасной. Это определяет границу. Публичные идентификаторы там жить могут; серверные учётные данные — нет.
2. Что означает переменная окружения в статическом приложении
На сервере Node.js переменная окружения читается во время выполнения из процесса. В сборке Vite, Create React App, Next.js или Astro публичные переменные окружения работают иначе: сборщик заменяет ссылки на литеральные строки во время сборки. Vite предоставляет переменные с префиксом VITE_ через import.meta.env; Create React App использует REACT_APP_; Next.js — NEXT_PUBLIC_; Astro — PUBLIC_. Префикс — это соглашение об именовании, а не средство защиты. Как только значение встроено, оно становится частью JavaScript, который скачивает каждый посетитель. Переменная без публичного префикса обычно недоступна клиентскому коду, поэтому серверные значения должны читать сервер или serverless-функция, а не статическая страница. Файл .env — это входные данные для разработки и сборки, а не хранилище секретов во время выполнения. Он полезен для локальной конфигурации и для того, чтобы держать значения вне системы контроля версий, но не делает значение приватным после сборки.
# Safe to expose: a public endpoint
VITE_API_URL=https://api.example.com
# Never expose: keep this on a server
STRIPE_SECRET_KEY=REPLACE_WITH_SERVER_SECRET 3. Какие ключи могут жить в браузере
Некоторые ключи созданы публичными. Публикуемый ключ Stripe идентифицирует аккаунт и создаёт платёжные токены; секретный ключ реально двигает деньги и должен оставаться на сервере. Anon-ключ Supabase безопасен только при корректных политиках row-level security. Веб-конфигурация Firebase идентифицирует проект, но не даёт административного доступа. Ключи карт и аналитики часто ограничиваются доменом или API. Эти значения можно отправлять в браузер, но им всё равно нужны ограничения: разрешённые origin, ограничения API, роли с минимальными правами, квоты и оповещения. Противоположную категорию перечислить проще. Ключи service role, пароли баз данных, учётные данные SMTP, секреты подписи и приватные ключи AI-провайдеров никогда не должны попадать в статический бандл. Ключ провайдера, принадлежащий приложению, особенно опасен: любой, кто его найдёт, сможет тратить деньги приложения. Ключ, принадлежащий пользователю, — другой случай. Это по-прежнему bearer-учётные данные, но они принадлежат пользователю, и цель — держать их в его браузере, а не в проекте или репозитории.
4. Как GB Coder обращается с учётными данными провайдеров
Панель ИИ в GB Coder работает по модели bring-your-own-key. Менеджер ключей по умолчанию хранит ключи провайдеров в памяти. Он не записывает их в localStorage. Пользователь может явно включить сохранение в sessionStorage; тогда значение ограничено текущей вкладкой и обычно очищается при её закрытии. Ключ удаляется из памяти и сессионного хранилища, когда пользователь его очищает. Менеджер также привязывает каждый ключ к разрешённому origin endpoint, а исходящий шлюз проверяет, что учётные данные не отправляются на неутверждённый адрес. Ошибки провайдеров проходят через слой редактирования, который удаляет шаблоны ключей и активные ключи до того, как сообщение попадёт в интерфейс или лог. Метаданные пользовательских провайдеров хранятся в localStorage, но заголовки, помеченные как секретные, очищаются перед сохранением и должны быть введены заново. Запросы идут напрямую из браузера в endpoint провайдера; GB Coder не проксирует ключ через сервер LadeStack. Эти меры уменьшают случайное сохранение и утечки между origin. Но они не меняют базовое правило: любой скрипт, который может выполняться на том же origin, может прочитать то, что читает страница. Используйте чистый профиль для чувствительной работы и не устанавливайте недоверенные расширения.
5. Секреты проекта, файлы .env и встраивание при сборке
Держите секреты вне дерева проекта. Добавьте .env и его локальные варианты в .gitignore, а в репозиторий коммитьте .env.example с именами-заполнителями. Если проект использует Vite, помните, что каждая переменная VITE_ встраивается в production-бандл. Значение, удобное при локальной разработке, становится публичным в момент, когда его используют в production-сборке. Перед деплоем проверьте сгенерированные ассеты на шаблоны ключей и рассматривайте находку как инцидент, а не предупреждение. Сканирование секретов и push protection в GitHub могут поймать некоторые известные форматы токенов, но это страховка, а не замена хранению значений вне репозитория. ZIP-экспорт браузерной IDE включает файлы проекта. Если файл .env находится в проекте, он уезжает вместе с резервной копией. Резервная копия — не зашифрованный сейф. Храните экспорты в приватном месте и удаляйте старые копии, когда они больше не нужны. То же относится к скриншотам, отчётам об ошибках и сообщениям в чате: ключ, вставленный в любое из этого, уже покинул браузер.
# .gitignore
.env
.env.local
.env.*.local 6. Токены GitHub и деплоя
Личные токены доступа GitHub и токены деплоя мощнее, чем кажутся. Классический токен с областями repository и workflow может читать приватный код, менять файлы и запускать релизы. Предпочитайте fine-grained токены с одним репозиторием, явным списком разрешений и сроком действия. Давайте токену только те права, которые нужны задаче: для синхронизации достаточно чтения и записи contents, без administration и billing. Создавайте отдельные токены для разных устройств или целей и отзывайте их по завершении работы. Токены деплоя Vercel или Netlify могут публиковать, заменять или удалять проекты. Если провайдер поддерживает ограничение по проекту или команде, используйте его; если поддерживает срок действия, задайте его. Вставляйте токен в панель деплоя только при необходимости и не сохраняйте его в файле проекта. В CI используйте секреты репозитория или организации, а не закоммиченный .env, и помните, что pull request из форков обычно не получают секретов. Токен, который попал на скриншот, был отправлен в чат или закоммичен в публичный репозиторий, следует считать скомпрометированным, даже если его удалили через секунду.
7. Ключи AI-провайдеров: BYOK без сюрпризов
Ключ AI-провайдера — это bearer-учётные данные с прикреплённым счётом. Любой, у кого он есть, может отправлять запросы, расходовать квоту и создавать затраты. Используйте ключ, ограниченный проектом, задайте лимит расходов, если провайдер это позволяет, и используйте отдельный ключ для экспериментов вместо мастер-ключа организации. Регулярно проверяйте использование, особенно после того, как поделились устройством или установили новое расширение. Локальные провайдеры вроде Ollama, LM Studio и vLLM не требуют облачного ключа, но их endpoint — всё равно сервис на машине. Не выставляйте локальный endpoint в публичный интернет только ради работающей демонстрации. Если пользовательский провайдер указывает на прокси, прокси видит ключ; используйте только тот прокси, который вы контролируете и понимаете. Prompt injection — связанный риск: файл, веб-страница или вставленное сообщение об ошибке могут содержать инструкции для ассистента. Держите секреты вне промптов и вне файлов, которые ассистенту разрешено читать. Если ключ появился в разговоре, ротируйте его.
8. Общие компьютеры, расширения и XSS
Общий или лабораторный компьютер требует другой процедуры. Используйте приватный профиль браузера, не включайте сохранение сессии и закрывайте вкладку по завершении. Если браузер предлагает сохранить ключ, откажитесь. Очистите хранилище сайта, если подозреваете, что оно сохранилось. Расширения браузера — тоже часть границы: расширение с широкими правами хоста часто может читать содержимое страницы или хранилище, а вредоносное обновление может прийти уже после установки. Используйте минимальный профиль для работы с учётными данными. Классический веб-риск в этом контексте — cross-site scripting. Если атакующий может выполнить скрипт на origin IDE, этот скрипт сможет читать localStorage и sessionStorage и отправлять запросы, пока страница открыта. Обновляйте зависимости, избегайте недоверенных сторонних скриптов и рассмотрите content security policy на задеплоенном сайте. Менеджеры буфера обмена и инструменты демонстрации экрана тоже видят, что вы копируете и показываете. Очищайте буфер обмена после переноса ключа и приостанавливайте демонстрацию перед открытием панели настроек провайдера.
9. Ротация утёкшего ключа
Ротация — единственный надёжный ответ на утёкший ключ. Сначала отозвать — важно: если старый ключ остаётся действительным, пока готовится замена, экспозиция продолжается. Создайте новый ключ с минимально необходимой областью, обновите приложение или настройки браузера, задеплойте заново, если значение входило в сборку, и проверьте, что старый ключ отклоняется. Затем изучите использование и журналы аудита провайдера на предмет неожиданной активности. Если ключ был закоммичен в Git, удалить файл недостаточно: значение остаётся в истории. Сначала ротируйте, затем перепишите историю инструментом вроде git filter-repo или BFG, сделайте force-push по согласованию с командой и попросите коллег клонировать репозиторий заново. Платформы хостинга могут кешировать старые сборки, поэтому убедитесь, что старое значение нельзя отдать из устаревшего бандла. Если ключ вставляли в чат с ИИ, тикет поддержки или скриншот, считайте, что он сохранён, и ротируйте его. Наконец, зафиксируйте, что произошло, добавьте проверку, которая могла бы это поймать, и сократите срок жизни следующего ключа.
10. Практический чек-лист
Используйте эту последовательность, прежде чем проект покинет браузер. Во-первых, классифицируйте каждое значение: публичный идентификатор, учётные данные пользователя или серверный секрет. Во-вторых, держите серверные секреты вне статического бандла и помещайте их за serverless-функцию или API. В-третьих, раскрывайте только значения, которым нужен публичный префикс, и проверяйте собранные ассеты перед деплоем. В-четвёртых, держите файлы .env вне Git, коммитьте .env.example и включите сканирование секретов. В-пятых, создавайте токены с ограниченной областью и сроком действия для GitHub и хостинга, по одному на задачу. В-шестых, в GB Coder оставляйте ключи в памяти, если вы не на доверенном устройстве, и включайте сохранение сессии только когда понимаете компромисс. В-седьмых, никогда не вставляйте ключ в промпт, тикет, скриншот или файл проекта. В-восьмых, ротируйте при любом сомнении и отзывайте до замены. В-девятых, используйте чистый профиль браузера на общих машинах. В-десятых, запишите план действий при утечке заранее. Браузерная IDE может ответственно обращаться с учётными данными, если относиться к браузеру как к публичному клиенту, а не как к приватному сейфу.