Проверка заголовков запросов

HTTP-заголовки используются для передачи метаданных запроса: информации об авторизации, типе содержимого, токенах, языке, пользовательском агенте и других параметрах. Некорректные или отсутствующие заголовки могут привести к ошибкам обработки запросов, уязвимостям безопасности и нарушениям API-контрактов.

Библиотека Validator.js позволяет выполнять валидацию значений заголовков при помощи набора встроенных функций.

Установка Validator.js

Для установки используется пакетный менеджер npm:

npm install validator

Подключение библиотеки:

const validator = require('validator');

Для ES-модулей:

import validator from 'validator';

Проверка заголовка Content-Type

Заголовок Content-Type определяет тип передаваемых данных.

Пример запроса:

Content-Type: application/json

Проверка значения:

const contentType = 'application/json';

const allowedTypes = [
    'application/json',
    'multipart/form-data',
    'text/plain'
];

const isValid = allowedTypes.includes(contentType);

console.log(isValid);

Проверка на пустое значение:

const contentType = '';

if (validator.isEmpty(contentType)) {
    console.log('Заголовок пуст');
}

Проверка строкового типа:

const contentType = 'application/json';

if (validator.isString(contentType)) {
    console.log('Корректная строка');
}

Проверка заголовка Authorization

Заголовок Authorization обычно содержит токены доступа.

Пример:

Authorization: Bearer eyJhbGciOi...

Проверка Bearer-токена

const authHeader = 'Bearer abc123token';

const bearerRegex = /^Bearer\s[\w-]+$/;

const isValid = bearerRegex.test(authHeader);

console.log(isValid);

Проверка JWT-токена

Validator.js содержит метод isJWT().

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';

const isValid = validator.isJWT(token);

console.log(isValid);

Извлечение токена из заголовка

const authHeader = 'Bearer my_secure_token';

const token = authHeader.split(' ')[1];

console.log(token);

Проверка отсутствия вредоносных символов

const authHeader = 'Bearer abc123';

if (validator.isAscii(authHeader)) {
    console.log('ASCII-символы допустимы');
}

Проверка заголовка Host

Заголовок Host указывает доменное имя сервера.

Пример:

Host: api.example.com

Проверка домена:

const host = 'api.example.com';

const isValid = validator.isFQDN(host);

console.log(isValid);

Проверка IP-адреса:

const host = '192.168.0.1';

const isValid = validator.isIP(host);

console.log(isValid);

Проверка localhost:

const host = 'localhost';

const isValid =
    host === 'localhost' ||
    validator.isIP(host) ||
    validator.isFQDN(host);

console.log(isValid);

Проверка User-Agent

Заголовок User-Agent содержит сведения о клиенте.

Пример:

User-Agent: Mozilla/5.0

Проверка длины строки

Слишком длинные значения могут использоваться в атаках.

const userAgent = 'Mozilla/5.0';

const isValid = validator.isLength(userAgent, {
    min: 3,
    max: 300
});

console.log(isValid);

Проверка на управляющие символы

const userAgent = 'Mozilla/5.0';

const hasInvalidChars = /[\r\n\t]/.test(userAgent);

console.log(!hasInvalidChars);

Проверка ASCII

const userAgent = 'Mozilla/5.0';

console.log(validator.isAscii(userAgent));

Проверка заголовка Origin

Заголовок Origin активно используется в CORS.

Пример:

Origin: https://example.com

Проверка URL

const origin = 'https://example.com';

const isValid = validator.isURL(origin, {
    protocols: ['http', 'https'],
    require_protocol: true
});

console.log(isValid);

Ограничение списка разрешённых источников

const allowedOrigins = [
    'https://example.com',
    'https://api.example.com'
];

const origin = 'https://example.com';

const isAllowed = allowedOrigins.includes(origin);

console.log(isAllowed);

Проверка Referer

Заголовок Referer содержит URL страницы-источника.

const referer = 'https://example.com/page';

const isValid = validator.isURL(referer);

console.log(isValid);

Проверка HTTPS:

const referer = 'https://example.com/page';

const isSecure = referer.startsWith('https://');

console.log(isSecure);

Проверка заголовков с числовыми значениями

Некоторые заголовки содержат числа:

Content-Length: 2048

Проверка целого числа

const contentLength = '2048';

const isValid = validator.isInt(contentLength, {
    min: 0,
    max: 10000000
});

console.log(isValid);

Преобразование значения

const length = parseInt(contentLength, 10);

console.log(length);

Проверка даты в заголовках

Пример:

If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

Проверка даты:

const dateHeader = 'Wed, 21 Oct 2015 07:28:00 GMT';

const isValid = validator.isDate(dateHeader);

console.log(isValid);

Проверка Accept-Language

Пример:

Accept-Language: en-US

Проверка локали:

const locale = 'en-US';

const isValid = validator.isLocale(locale);

console.log(isValid);

Проверка списка языков:

const languages = 'en-US,en;q=0.9,ru;q=0.8';

const items = languages.split(',');

for (const item of items) {
    console.log(item.trim());
}

Проверка ETag

ETag используется для кеширования.

Пример:

ETag: "abc123"

Проверка строки:

const etag = '"abc123"';

const isValid = validator.isLength(etag, {
    min: 3,
    max: 100
});

console.log(isValid);

Проверка формата:

const etagRegex = /^W\/"[\w.-]+"$|^"[\w.-]+"$/;

console.log(etagRegex.test(etag));

Проверка API-ключей в заголовках

Пример:

X-API-Key: sk_test_123456

Проверка длины

const apiKey = 'sk_test_123456';

const isValid = validator.isLength(apiKey, {
    min: 10,
    max: 128
});

console.log(isValid);

Проверка допустимых символов

const apiKey = 'sk_test_123456';

const isValid = validator.matches(
    apiKey,
    /^[a-zA-Z0-9_-]+$/
);

console.log(isValid);

Проверка кастомных заголовков

Пользовательские заголовки обычно имеют префикс X-.

Пример:

X-Client-Version: 1.4.2

Проверка семантической версии:

const version = '1.4.2';

const isValid = validator.isSemVer(version);

console.log(isValid);

Комплексная проверка заголовков

Пример middleware для Express

const validator = require('validator');

function validateHeaders(req, res, next) {
    const auth = req.headers.authorization;
    const origin = req.headers.origin;
    const contentType = req.headers['content-type'];

    if (!auth || !/^Bearer\s[\w-]+$/.test(auth)) {
        return res.status(400).json({
            error: 'Некорректный Authorization'
        });
    }

    if (
        origin &&
        !validator.isURL(origin, {
            require_protocol: true
        })
    ) {
        return res.status(400).json({
            error: 'Некорректный Origin'
        });
    }

    const allowedTypes = [
        'application/json'
    ];

    if (!allowedTypes.includes(contentType)) {
        return res.status(400).json({
            error: 'Недопустимый Content-Type'
        });
    }

    next();
}

Использование middleware:

const express = require('express');
const app = express();

app.use(validateHeaders);

Санитизация заголовков

Validator.js содержит методы очистки строк.

Удаление пробелов

const header = '   application/json   ';

const clean = validator.trim(header);

console.log(clean);

Экранирование HTML

const header = '<script>alert(1)</script>';

const safe = validator.escape(header);

console.log(safe);

Нормализация значений

const header = 'APPLICATION/JSON';

const normalized = header.toLowerCase();

console.log(normalized);

Защита от Header Injection

Header Injection — атака, связанная с внедрением управляющих символов.

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

const value = 'test\r\nX-Injected: true';

Проверка:

function hasHeaderInjection(value) {
    return /[\r\n]/.test(value);
}

console.log(hasHeaderInjection(value));

Безопасная обработка:

function sanitizeHeader(value) {
    return value.replace(/[\r\n]+/g, '');
}

console.log(sanitizeHeader(value));

Проверка обязательных заголовков

const requiredHeaders = [
    'authorization',
    'content-type'
];

function validateRequiredHeaders(headers) {
    return requiredHeaders.every(
        header => headers[header]
    );
}

Пример использования:

const headers = {
    authorization: 'Bearer token',
    'content-type': 'application/json'
};

console.log(validateRequiredHeaders(headers));

Централизованная схема проверки

const validators = {
    authorization: value =>
        /^Bearer\s[\w-]+$/.test(value),

    origin: value =>
        validator.isURL(value, {
            require_protocol: true
        }),

    'content-type': value =>
        ['application/json'].includes(value),

    'content-length': value =>
        validator.isInt(value, {
            min: 0
        })
};

Проверка всех заголовков:

function validateHeaders(headers) {
    const errors = [];

    for (const key in validators) {
        const validate = validators[key];
        const value = headers[key];

        if (value && !validate(value)) {
            errors.push(`Ошибка заголовка: ${key}`);
        }
    }

    return errors;
}

Использование:

const result = validateHeaders({
    authorization: 'Bearer token',
    origin: 'https://example.com',
    'content-type': 'application/json',
    'content-length': '120'
});

console.log(result);

Рекомендации по проверке заголовков

Ограничение размеров

Слишком большие заголовки могут приводить к переполнению памяти и DoS-атакам.

function validateSize(value) {
    return validator.isLength(value, {
        max: 1024
    });
}

Белые списки вместо чёрных

Предпочтительно явно разрешать допустимые значения:

const allowed = [
    'application/json',
    'text/plain'
];

allowed.includes(value);

Проверка кодировки

validator.isAscii(headerValue);

Нормализация регистра

HTTP-заголовки нечувствительны к регистру.

const normalizedHeaders = {};

for (const key in headers) {
    normalizedHeaders[key.toLowerCase()] =
        headers[key];
}

Логирование ошибок

try {
    validateHeaders(req.headers);
} catch (error) {
    console.error(error.message);
}

Интеграция с Express

Полный пример:

const express = require('express');
const validator = require('validator');

const app = express();

app.use((req, res, next) => {
    const auth = req.headers.authorization;

    if (!auth) {
        return res.status(401).json({
            error: 'Authorization required'
        });
    }

    if (!/^Bearer\s[\w-]+$/.test(auth)) {
        return res.status(400).json({
            error: 'Invalid token format'
        });
    }

    const userAgent = req.headers['user-agent'];

    if (
        userAgent &&
        !validator.isLength(userAgent, {
            min: 3,
            max: 300
        })
    ) {
        return res.status(400).json({
            error: 'Invalid User-Agent'
        });
    }

    next();
});

app.get('/', (req, res) => {
    res.send('OK');
});

app.listen(3000);