Безопасность: helmet, CORS, rate-limit

helmet, cors и rate-limit — три кита базовой безопасности любого Express-API.
«Эти три middleware решают разные задачи и работают вместе — ставь все три по умолчанию.»

Открытое в интернет приложение мгновенно становится мишенью: боты сканируют порты, перебирают пароли, ищут дыры. Минимальная защита Express строится на трёх middleware: helmet ставит безопасные заголовки, cors контролирует кросс-доменный доступ, rate-limit ограничивает частоту запросов. В этом уроке разберём каждый.

helmet: защитные заголовки

helmet — это набор маленьких middleware, которые выставляют HTTP-заголовки, прикрывающие типовые уязвимости (clickjacking, угадывание типа контента и т.д.):

const helmet = require('helmet');
app.use(helmet());  // одна строка -- десяток защитных заголовков
// X-Content-Type-Options, X-Frame-Options,
// Strict-Transport-Security и другие

По словам авторов, helmet — самое высокоэффективное одиночное улучшение безопасности Express: одна строка закрывает целый класс проблем.

cors: кто может звать твой API

Браузер по умолчанию запрещает странице с одного домена звать API на другом. CORS-заголовки явно разрешают доверенные источники:

const cors = require('cors');
// разрешаем только свой фронтенд, а не всех подряд
app.use(cors({ origin: 'https://myapp.ru' }));

Открывать CORS всем (origin: '*') для API с авторизацией опасно — указывай конкретные домены.

rate-limit: защита от перебора

const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // окно 15 минут
  max: 100                   // не больше 100 запросов с одного IP
});
app.use(limiter);

Порядок подключения

app.use(helmet())      -> 1. базовая защита заголовками
app.use(cors(...))     -> 2. правила кросс-доменного доступа
app.use(limiter)       -> 3. ограничение частоты
app.use(express.json())-> 4. парсинг тела
маршруты               -> 5. логика
обработчик ошибок      -> последним

Скользящее окно лимита в браузере

Смоделируем rate limiter: считаем запросы в окне времени и блокируем превышение. Это упрощённая версия того, что делает express-rate-limit:

function createLimiter(maxRequests, windowMs) {
  let hits = [];
  return function allow(now) {
    hits = hits.filter(t => now - t < windowMs);  // выкидываем старые
    if (hits.length >= maxRequests) return false; // лимит исчерпан
    hits.push(now);
    return true;
  };
}

const allow = createLimiter(3, 1000); // 3 запроса в секунду
console.log(allow(0));   // true
console.log(allow(100)); // true
console.log(allow(200)); // true
console.log(allow(300)); // false -- превышен лимит
console.log(allow(1300));// true  -- окно сдвинулось

Как работает под капотом

helmet просто добавляет middleware, которые на каждый ответ дописывают заголовки безопасности — браузер их читает и применяет защиту. cors отвечает на предварительный запрос браузера (preflight OPTIONS) и проставляет заголовки Access-Control-Allow-*. rate-limit держит счётчик запросов по IP в окне времени и при превышении возвращает 429 Too Many Requests, не пуская запрос дальше по цепочке.

Частые ошибки

  • origin: '*' с куками/токенами. Это открывает API всему интернету. Указывай конкретные домены.
  • Лимитер после маршрутов. Защита должна стоять до логики, иначе она бесполезна.
  • Считать helmet достаточным. Это базовый минимум, а не полная защита; нужны ещё валидация и аутентификация.

Best practices

  • Подключай helmet, cors и rate-limit как стандартный базовый набор любого API.
  • Ставь их в начале цепочки, до парсеров и маршрутов.
  • Особо строгий лимит — на чувствительные маршруты вроде /login.

Итоги

helmet, cors и rate-limit — три разных, дополняющих друг друга средства защиты. Они закрывают базу, но не отменяют валидацию данных и аутентификацию. Дальше разберём, как хранить секреты приложения и как переключаться между окружениями разработки и продакшена.

Глубина важнее одной строки

helmet, cors и rate-limit закрывают периметр, но безопасность — это слои. Главные угрозы веб-приложений живут глубже: инъекции (когда непроверенные данные попадают в SQL или команды), некорректная аутентификация, утечки чувствительных данных в логах и ответах. Ни один middleware не спасёт от того, что ты сам подставил req.body в запрос к базе без валидации. Поэтому относись к трём базовым пакетам как к первому слою, а не к финальному рубежу: за ними обязаны идти строгая валидация любого ввода, параметризованные запросы через ORM, минимизация данных в ответах и аккуратное обращение с секретами. Безопасность — это привычка на каждом маршруте, а не одна строка в начале файла.

Проверьте себя
1. Что делает helmet()?
AОграничивает число запросов
BВыставляет набор защитных HTTP-заголовков
CШифрует тело ответа
DХранит секреты приложения
2. Почему опасно ставить cors с origin: '*' для API с авторизацией?
AЭто замедляет ответы
BЛюбой сайт сможет обращаться к твоему API из браузера
CCORS перестанет работать
DExpress выдаст ошибку запуска