Environment variables безопасность

Fresh — фреймворк для Deno, изначально ориентированный на безопасность, предсказуемость и минимальное количество магии. Работа с переменными окружения в Fresh подчиняется философии Deno: явные разрешения, отсутствие неявного доступа к системе и строгий контроль над тем, какие данные доступны коду. Это делает тему безопасности переменных окружения принципиально важной.

Переменные окружения используются для хранения чувствительных данных: токенов API, ключей доступа к базам данных, секретов OAuth, параметров окружений (development, staging, production). Ошибки в их использовании приводят к утечкам, особенно в server-side rendering и edge-сценариях.


Модель безопасности Deno и доступ к переменным окружения

В Deno доступ к переменным окружения запрещён по умолчанию. Для чтения Deno.env требуется явное разрешение:

deno run --allow-env main.ts

Или более узко:

deno run --allow-env=DATABASE_URL,API_KEY main.ts

Ключевые особенности:

  • отсутствует глобальный process.env
  • доступ осуществляется через Deno.env.get()
  • разрешения проверяются на уровне рантайма

Это исключает целый класс уязвимостей, характерных для Node.js-приложений, где любой зависимый пакет может прочитать все переменные окружения.


Переменные окружения в Fresh-приложении

Fresh-приложение запускается через deno task start или deno run. Обычно разрешения задаются в deno.json:

{
  "tasks": {
    "start": "deno run --allow-net --allow-env --allow-read main.ts"
  }
}

С точки зрения безопасности предпочтительно:

  • минимизировать список разрешённых переменных
  • не использовать --allow-env без ограничений в production
  • разделять конфигурацию для разных окружений

Серверный и клиентский контекст

Fresh чётко разделяет серверный и клиентский код. Это критично для безопасности переменных окружения.

Серверный код:

  • routes/*.ts
  • routes/*.tsx (в части обработчиков)
  • islands — только частично (см. ниже)

Клиентский код:

  • islands/*.tsx (код, выполняемый в браузере)
  • любой код, попадающий в бандл

Переменные окружения никогда не должны напрямую использоваться в клиентском коде. Любое значение, попавшее в JSX, сериализуется и отправляется в браузер.

Пример опасной ошибки:

export default function Page() {
  return <div>{Deno.env.get("API_KEY")}</div>;
}

Даже если компонент кажется серверным, значение будет встроено в HTML-ответ.


Безопасный паттерн: изоляция конфигурации

Рекомендуемый подход — централизованный конфигурационный модуль:

// config/env.ts
export const DATABASE_URL = Deno.env.get("DATABASE_URL");
export const API_KEY = Deno.env.get("API_KEY");

С дополнительной валидацией:

function requireEnv(name: string): string {
  const value = Deno.env.get(name);
  if (!value) {
    throw new Error(`Missing environment variable: ${name}`);
  }
  return value;
}

export const DATABASE_URL = requireEnv("DATABASE_URL");

Преимущества:

  • единая точка доступа
  • раннее обнаружение ошибок
  • проще контролировать, где используются секреты

Fresh islands и риск утечек

Islands в Fresh выполняются как на сервере (при первом рендере), так и в браузере. Это делает их особенно опасными с точки зрения переменных окружения.

Запрещённый паттерн:

export default function Counter() {
  const secret = Deno.env.get("SECRET");
  return <button>{secret}</button>;
}

Даже если значение используется только для логики, оно может попасть в сериализованное состояние.

Безопасное правило:

  • islands не должны импортировать модули, содержащие секреты
  • вся логика с переменными окружения остаётся в серверных обработчиках

Передача данных вместо передачи секретов

Если требуется использовать данные, зависящие от переменных окружения, используется серверная абстракция:

// routes/api/config.ts
export const handler = () => {
  return new Response(
    JSON.stringify({ featureEnabled: true }),
    { headers: { "Content-Type": "application/json" } },
  );
};

Клиентский код работает только с производными данными, а не с самими секретами.


Работа с .env-файлами

Deno не загружает .env автоматически. Для разработки часто используется стандартная библиотека:

import "https://deno.land/std/dotenv/load.ts";

Особенности безопасности:

  • .env не должен попадать в репозиторий
  • значения из .env равноправны системным переменным
  • в production предпочтительнее использовать переменные среды платформы (Fly.io, Deno Deploy, Docker)

В Fresh .env используется только на сервере, при старте приложения.


Deno Deploy и переменные окружения

В Deno Deploy переменные окружения задаются через интерфейс или CLI и доступны через Deno.env.get.

Ограничения среды:

  • отсутствует файловая система
  • невозможна загрузка .env
  • все переменные считаются production-секретами

Это снижает риск утечек, но требует строгой дисциплины в коде, особенно при использовании SSR.


Логирование и утечки через ошибки

Одна из распространённых уязвимостей — вывод переменных окружения в логах или ошибках.

Опасные примеры:

console.error(Deno.env.toObject());
throw new Error(`Config: ${JSON.stringify(Deno.env.toObject())}`);

Даже в закрытых логах это создаёт риск утечки через:

  • системы мониторинга
  • crash-репорты
  • сторонние лог-агрегаторы

Безопасный подход:

  • логировать только факт отсутствия переменной
  • не логировать значения секретов
  • использовать маскирование

Минимизация области видимости секретов

Принцип наименьших привилегий применим и к переменным окружения:

  • разные ключи для разных сервисов
  • разные переменные для разных окружений
  • отсутствие универсальных SUPER_SECRET

В Fresh это усиливается за счёт разрешений Deno — можно ограничить доступ к конкретным именам переменных.


Типизация и безопасность

TypeScript позволяет снизить количество ошибок конфигурации:

interface Env {
  DATABASE_URL: string;
  API_KEY: string;
}

И последующая проверка при старте приложения. Это не защищает от утечек напрямую, но снижает риск неявного использования неправильных переменных.


Основные риски и их предотвращение

Типовые угрозы:

  • попадание секретов в HTML
  • утечка через islands
  • логирование конфигурации
  • избыточные разрешения --allow-env

Механизмы защиты в Fresh:

  • строгая модель Deno permissions
  • разделение server/client
  • отсутствие глобального process.env
  • контроль над сериализацией состояния

Fresh не скрывает работу с переменными окружения, а делает её максимально явной. Это требует дисциплины, но значительно повышает уровень безопасности при правильной архитектуре.