---
sidebar_label: "Обзор безопасности"
title: "Обзор безопасности - CyberGo html | меры защиты"
description: "Обзор безопасности CyberGo html: лимит ввода, глубина DOM, защита от обхода пути, восстановление после паник, тайм-ауты и подключаемый аудит."
sidebar_position: 1
---

# Обзор безопасности

Библиотека HTML обрабатывает ненадёжный ввод из интернета, поэтому безопасность является её главным приоритетом в дизайне. Библиотека включает встроенные многоуровневые независимые механизмы защиты, следуя принципу **эшелонированной защиты**: каждый уровень предполагает, что другие могут отказать, и обход одного уровня не приводит к компрометации всей системы.

Эта страница — обзор функций безопасности. Если вы хотите сразу приступить к настройке конвейера аудита или проверке предзапускового чек-листа, перейдите к разделу [Следующие шаги](#следующие-шаги) в конце.

## Архитектура эшелонированной защиты

Защита библиотеки распределена по трём независимым уровням, каждый со своими режимами отказа и стратегиями восстановления:

```text
Защита на уровне ввода (отклонение перед фильтрацией)
├── MaxInputSize        Байтовый лимит размера (по умолчанию 50 МБ)
├── MaxDepth            Лимит глубины вложенности DOM (по умолчанию 500, защита от переполнения стека)
├── ProcessingTimeout   Тайм-аут обработки одного документа (по умолчанию 30 с)
├── Обнаружение обхода пути   Компоненты .. в файловых путях
└── AllowedBaseDir      Песочница чтения файлов (разрешение через дескриптор ОС, защита от symlink/junction)

Защита на уровне обработки (санирование и отмена)
├── HTML-санирование    Многомерная фильтрация тегов / атрибутов / URL / CSS
├── Кооперативная отмена context  ExtractWithContext реагирует на ctx.Done()
├── Восстановление Panic Обёртка recoverPanic, возвращает ErrInternalPanic
└── Защита от утечки Goroutine   maxTimeoutGoroutines (лимит 1000)

Защита на уровне аудита (наблюдаемость + изоляция)
├── Изоляция panic AuditSink SEC-003 (подсистема аудита — best-effort)
├── 8 типов событий аудита  blocked_tag/blocked_attr/blocked_url/...
└── HTML-экранирование исходных значений   Предотвращает XSS в журналах аудита на дашбордах SIEM
```

:::tip Подсказка
Почему акцент на «независимости»? Ценность эшелонированной защиты — в отсутствии предположений о связности между уровнями. Например, даже если уровень санирования пропустит вредоносный URL (отказ уровня обработки), `MaxInputSize` на уровне ввода всё равно заблокирует слишком длинный payload; даже если AuditSink сам вызовет panic (отказ уровня аудита), `recoverPanic` гарантирует, что основной процесс не завершится аварийно.
:::

## Защита границ ввода

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

По умолчанию максимальный размер ввода — 50 МБ (`DefaultMaxInputSize`), защита от атак на исчерпание памяти. Верхний предел конфигурации также ограничен `maxConfigInputSize` (те же 50 МБ), то есть нельзя установить небезопасное значение через конфигурацию:

```go
cfg := html.DefaultConfig()
cfg.MaxInputSize = 10 * 1024 * 1024 // Сужение до 10 МБ
```

Для файловых путей также существует **предварительная проверка**: после получения размера через `Stat` файл, превышающий лимит, отклоняется до загрузки содержимого в память через `ReadAll`, устраняя окно пикового использования памяти по сценарию «сначала полный reads, затем обнаружение превышения».

### Ограничение глубины DOM

По умолчанию максимальная глубина — 500 (`DefaultMaxDepth`), защита от стековых бомб с рекурсивной вложенностью:

```go
cfg.MaxDepth = 200 // Строже
```

Проверка глубины использует **итеративный** обход, а не рекурсию, и сама по себе не вызывает переполнение стека при слишком глубоко вложенном вводе.

### Тайм-аут обработки

Настраиваемый тайм-аут обработки предотвращает экспоненциальный рост времени обработки при злонамеренном HTML:

```go
cfg.ProcessingTimeout = 10 * time.Second
```

Механизм тайм-аута реализован через отдельный goroutine + context deadline и защищён верхним пределом `maxTimeoutGoroutines` (1000), предотвращающим выход goroutine из-под контроля при высокой нагрузке. В сочетании с `ExtractWithContext` можно добавить сигнал отмены от вызывающего.

## Подробное описание механизма санирования контента

Санирование управляется параметром `EnableSanitization` (по умолчанию `true`), который модифицирует распарсенное DOM-дерево на месте, удаляя потенциально вредоносное содержимое. Весь процесс реализован в `internal/sanitize.go`, каждое перехваченное действие записывается в журнал аудита.

:::warning Предупреждение
Отключать санирование следует только если ввод — **полностью доверенные** внутренние данные. При обработке любого HTML из пользовательских загрузок, веб-скрапинга, сторонних API санирование должно быть включено.
:::

### Удаляемые теги

Следующие теги удаляются вместе с их поддеревом:

| Тег | Причина удаления |
|-----|-------------------|
| `<script>`, `<style>`, `<noscript>` | Контейнеры скриптов и стилей, могут содержать исполняемый код или скрытые payload-ы |
| `<iframe>`, `<embed>`, `<object>` | Встраивание внешнего содержимого, классические векторы XSS и фишинга |
| `<input>`, `<button>` | Элементы управления формы, могут использоваться для CSRF или UI-подмены |
| `<svg>` | Может содержать встроенный JavaScript и обработчики событий |
| `<math>` | MathML, в некоторых браузерах может быть использован для выполнения скриптов |

:::tip Подсказка
Почему `<form>` не удаляется? Тег `<form>` **намеренно сохраняется**. Серверные фреймворки ASP.NET WebForms, JSF, JSP оборачивают весь `<body>` в один `<form>`, и удаление `<form>` привело бы к потере всего видимого содержимого страницы. Текстовое извлечение не рендерит и не отправляет формы, поэтому аргументы CSRF/UI-подмены, применимые к `<input>`/`<button>`, не применимы к самому контейнеру `<form>`.
:::

### Удаляемые атрибуты

| Категория атрибутов | Метод обнаружения | Пример |
|---------------------|-------------------|--------|
| Обработчики событий | Префиксное сопоставление `on*` | `onclick`, `onerror`, `onmouseover` |
| Переопределение form action | Точное сопоставление | `formaction` (может перехватить целевую отправку формы) |
| Автофокус | Точное сопоставление | `autofocus` (может использоваться для фишинга и принуждения к клику) |

Обнаружение обработчиков событий использует префиксное сопоставление (имя атрибута начинается с `on`), что позволяет покрывать все существующие и будущие варианты `on*`, а не полагаться на фиксированный чёрный список.

### Опасные CSS-паттерны

Значения атрибута `style` проверяются на следующие опасные подстроки; при совпадении **весь style удаляется** (сохранение безопасных CSS-свойств для извлечения метаданных требует отдельной оценки, текущая стратегия — полное удаление при обнаружении опасности):

- `expression(` — динамические выражения старых версий IE
- `behavior:` — привязка поведений IE
- `-moz-binding:` — привязка XBL старых версий Firefox
- `javascript:` — инъекция протокола
- `vbscript:` — инъекция протокола

### Проверяемые URI-атрибуты

Значения следующих атрибутов проходят через конвейер [URL-защиты](#глубина-url-защиты) для проверки протокола и data URL:

```text
href  src  cite  action  data  poster  background
longdesc  usemap  profile  xlink:href
```

:::tip Подсказка
Полностью заблокированные атрибуты не проверяются повторно. `formaction` уже заблокирован в разделе «Удаляемые атрибуты», поэтому он не попадает в конвейер URI-проверки — чтобы избежать избыточной проверки одного и того же атрибута.
:::

## Глубина URL-защиты

Значения URI-атрибутов проверяются через многоуровневый конвейер `isSafeURIWithAudit`. Каждый уровень соответствует известному поведению браузерного парсинга или технике обхода защиты — ни один нельзя пропустить.

### Многоуровневый конвейер проверки

```text
Исходный URI
  │
  ├─ 1. NFC-нормализация        Нормализация полноширинных/комбинированных символов
  ├─ 2. TrimSpace               Удаление пробелов в начале и конце
  ├─ 3. Удаление C0-управляющих + пробелов  Удаление начальных/конечных U+0000–U+001F и пробелов
  ├─ 4. Удаление tab/LF/CR      Удаление \t \n \r внутри URL
  ├─ 5. ToLower                 Нормализация регистра
  ├─ 6. Лимит длины             Не-data URL ограничены MaxURLLength(2000)
  ├─ 7. Проверка опасных протоколов  javascript: / vbscript: / file: + полноширинные варианты
  ├─ 8. Проверка protocol-relative URL  //javascript: и др.
  └─ 9. Белый список data URL   Только изображения/шрифты/PDF, блокировка svg и пустых MIME
```

### Unicode-нормализация (NFC)

Первый шаг выполняет NFC-нормализацию URI (`normalizeURIForSecurity`), предотвращая маскировку опасных протоколов с помощью Unicode-преобразований:

- Полноширинные символы `ｊａｖａｓｃｒｉｐｔ：` отображаются обратно в ASCII
- Комбинированные символы, кросс-скриптовые похожие символы нормализуются к единому представлению

Поверх этого `isDangerousScheme` также выполняет отдельное ASCII-свёртывание полноширинных латинских символов (U+FF01–U+FF5E) через `normalizeFullwidthToASCII`, обеспечивая двойную страховку — даже если некоторые браузеры/парсеры обрабатывают полноширинные формы как ASCII, библиотека их распознает.

### Удаление пробелов и управляющих символов

Браузеры при парсинге URL удаляют определённые управляющие символы (следуя стандарту WHATWG URL), и библиотека должна симулировать то же удаление **до** проверки протокола, иначе злоумышленник может использовать эти символы для разбиения имени опасного протокола и обхода проверки:

- **tab / LF / CR**: `java\tscript:` будет собран браузером обратно в `javascript:` и выполнен. Библиотека использует `stripURLWhitespace` для удаления этих трёх байтов перед проверкой протокола.
- **C0-управляющие символы (U+0000–U+001F) + ASCII-пробелы**: браузеры удаляют начальные и конечные байты перед парсингом схемы. `strings.TrimSpace` покрывает только Unicode-пробелы, но не большинство C0-управляющих символов, поэтому библиотека использует отдельное множество `c0ControlOrSpace` для явного удаления. Иначе `\x01javascript:…` сможет обойти все проверки `HasPrefix`.

### Обнаружение опасных протоколов

| Протокол | Метод блокировки |
|----------|-------------------|
| `javascript:` | Прямое сопоставление + нормализация полноширинных символов |
| `vbscript:` | То же |
| `file:` | То же (доступ к локальной файловой системе) |
| `//javascript:`, `//vbscript:`, `//data:`, `//file:` | Протокол-относительные формы проверяются отдельно |

Проверка протокол-относительных форм (начинающихся с `//`) сначала удаляет начальные пробелы после `//`, затем применяет то же определение опасного протокола, что гарантирует невозможность обхода для вариантов типа `// javascript:`.

### Белый список data URL

Data URL разрешают только следующие явно объявленные MIME-типы:

| Категория | Разрешённые MIME-типы |
|-----------|----------------------|
| Изображения | `image/gif`, `image/jpeg`, `image/jpg`, `image/png`, `image/webp`, `image/bmp`, `image/x-icon`, `image/vnd.microsoft.icon`, `image/avif`, `image/apng` |
| Шрифты | `font/woff`, `font/woff2`, `font/ttf`, `font/otf`, `application/font-woff`, `application/font-woff2` |
| Документы | `application/pdf` |

Явно блокируются:

- **`image/svg+xml`**: SVG может содержать встроенный JavaScript — работает как патч эшелонированной защиты после удаления тега.
- **Пустые медиа-типы**: например `data:;base64,<payload>` или `data:;,...`. Эти формы ранее могли обходить белый список, теперь отклоняются напрямую.
- **Сверхдлинные data URL**: ограничены `MaxDataURILength` (100 КБ), предотвращая исчерпание памяти большими блоками base64.
- **Недопустимые символы base64**: base64-часть посимвольно проверяется на корректность набора символов.

:::tip Подсказка
Журнал аудита обрезает data URL. Data URL может содержать длинный base64, и полная запись в журнал аудита — пустая трата места и возможная утечка встроенного конфиденциального содержимого. Записи аудита обрезаются через `truncateAuditURL` до 256 символов.
:::

### Пути обхода санирования

Несколько путей кода (например `ExtractAllLinks`, сканирование video/audio в исходном HTML) читают **несанированный** HTML. Эти пути защищены через `containsDangerousScheme` — он использует тот же конвейер нормализации, что и санизатор (NFC, trim, удаление C0, удаление tab/LF/CR, свёртывание полноширинных), гарантируя, что оба пути выполняют **одну и ту же политику протоколов**, без несоответствий вида «санизатор блокирует, а здесь пропускается».

Например, payload типа `javascript:alert(1).mp4`, замаскированный под медиа-URL, ранее мог пройти простую проверку (первый символ `j` — буква), теперь перехватывается `containsDangerousScheme`.

## Механизм песочницы AllowedBaseDir

При обработке файловых путей из ненадёжных источников через `ExtractFromFile`, `AllowedBaseDir` ограничивает область чтения указанным каталогом. Механизм реализован функциями `readContained` / `realPath` / `pathWithin` в `processor.go`.

### Почему нужно разрешение через дескриптор ОС

Обычный `filepath.EvalSymlinks` **не может** разрешить Windows directory junction и reparse points — а они создаются без каких-либо привилегий и являются основным средством обхода ограничений пути в Windows. Подход библиотеки:

1. `os.Open()` открывает целевой файл, получая дескриптор ОС
2. `realPath(f)` разрешает реальный путь на диске из **уже открытого дескриптора**
3. `pathWithin(realBase, realTarget)` определяет, попадает ли реальный путь в разрешённый каталог
4. Из **того же проверенного дескриптора** `io.ReadAll` читает содержимое

Чтение и проверка из одного дескриптора закрывает окно TOCTOU (time-of-check-to-time-of-use) — подмена пути на символическую ссылку между проверкой и чтением не может повлиять на результат, поскольку читается тот самый inode, который был проверен.

### Кроссплатформенное разрешение реального пути

| Платформа | Метод разрешения | Покрываемые типы перенаправления |
|-----------|------------------|----------------------------------|
| Linux | Чтение link из `/proc/self/fd/<fd>` | Символические ссылки (race-free) |
| macOS / BSD | Чтение link из `/dev/fd/<fd>` | Символические ссылки (race-free) |
| Прочие Unix | Откат к `filepath.EvalSymlinks` | Символические ссылки (остаточный лёгкий TOCTOU) |
| Windows | `GetFinalPathNameByHandleW` | Символические ссылки + junction + все reparse points |

Возвращённый Windows путь очищается от префикса расширенной длины `\\?\` и обрабатывается через `Clean`, приводя к формату, согласованному с выводом `filepath.Abs`, что обеспечивает точность последующего сравнения включения.

### Уровни защиты

Чтение файлов в режиме `AllowedBaseDir` накладывает четыре независимые проверки:

1. **Обнаружение обхода пути**: после `filepath.Clean` проверяется наличие компонентов `..`
2. **Песочница через дескриптор ОС**: `realPath` разрешает реальный путь, `pathWithin` определяет включение
3. **Предварительная проверка размера**: `Stat` на проверенном дескрипторе, файлы сверх `MaxInputSize` отклоняются до `ReadAll`
4. **Байтовый лимит**: после чтения `validateInput` повторно проверяет количество байтов

Даже если файл находится в разрешённом каталоге, `AllowedBaseDir` ограничивает «какой файл можно прочитать», а `MaxInputSize` ограничивает «какого размера файл может быть» — эти ограничения ортогональны и не заменяют друг друга.

:::warning Предупреждение
Пустой `AllowedBaseDir` = песочница не активна. `AllowedBaseDir` по умолчанию — пустая строка, означающая **отсутствие ограничения по каталогу** (сохраняется только проверка обхода пути через `..`). Если ваши файловые пути поступают от пользователя, обязательно явно установите этот параметр.
:::

### Пример конфигурации

```go
cfg := html.DefaultConfig()
// Разрешить чтение файлов только из /var/app/uploads и его подкаталогов
cfg.AllowedBaseDir = "/var/app/uploads"

p, err := html.New(cfg)
if err != nil {
    log.Fatal(err)
}
defer p.Close()

// Нормально: файл в разрешённом каталоге
result, err := p.ExtractFromFile("/var/app/uploads/page.html")

// Отклонено: указывает наружу через symlink/junction
_, err = p.ExtractFromFile("/var/app/uploads/escape.txt") // Если это junction → /etc
```

Отклонённый выход за границы записывается как событие аудита `AuditEventPathTraversal` и возвращает `*FileError`, обёрнутый сообщением "path outside allowed directory"; ошибка **не содержит** разрешённый реальный путь (чтобы избежать утечки структуры файловой системы).

## Восстановление после паник и изоляция

Все публичные методы извлечения обёрнуты в обобщённую функцию `recoverPanic[T]`, panic перехватывается и преобразуется в ошибку `ErrInternalPanic`, гарантируя, что злонамеренный ввод не приведёт к аварийному завершению процесса вызывающего.

```go
func recoverPanic[T any](fn func() (T, error)) (result T, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("%w: %v", ErrInternalPanic, r)
        }
    }()
    return fn()
}
```

### Многоуровневые границы изоляции

| Граница изоляции | Поведение | Расположение |
|------------------|-----------|--------------|
| Однократное извлечение | panic → `ErrInternalPanic` | `extract.go` `recoverPanic` |
| Отдельный элемент пакета | Независимый recover для каждого элемента, panic одного не влияет на другие | `batch.go` |
| Goroutine тайм-аута | Независимый recover в worker-goroutine `withTimeout` | `extract.go` |
| Запись в AuditSink | panic самого sink подавляется (SEC-003) | `audit.go` `Record` |
| Закрытие AuditSink | panic sink.Close оборачивается в `ErrInternalPanic` и возвращается через error | `audit.go` `Close` |
| Создание пула Processor | panic в `sync.Pool.New` → `ErrInternalPanic` | `processor_pool.go` |

### SEC-003: подсистема аудита — best-effort

Подсистема аудита **никогда** не должна распространять panic до вызывающего публичного API. `AuditSink`, переданный пользователем (возможно, самописный, возможно, с фильтрами/мультиплексорами), может вызвать panic в `Write()` на любом пути. `Record` использует `defer recover()` для подавления таких panic (восстановленное значение отбрасывается — у пути аудита нет безопасного канала отчётности); `Close`, имея возвращаемое значение error, оборачивает восстановленное значение в `ErrInternalPanic`.

Это означает, что даже если в вашем пользовательском Sink есть ошибка, идиома `defer processor.Close()` не приведёт к аварийному завершению процесса.

### Защита от утечки Goroutine

`withTimeout` при каждом вызове создаёт worker-goroutine для ожидания deadline. Для предотвращения выхода goroutine из-под контроля при высокой нагрузке глобальный счётчик `activeTimeoutGoroutines` ограничивает параллелизм до `maxTimeoutGoroutines` (1000). При превышении новые запросы немедленно возвращают `ErrProcessingTimeout` вместо бесконечного накопления goroutine (при оценке ~1 МБ стека на goroutine, 1000 ≈ 1 ГБ верхний предел).

### Защита от инъекций в журнал через исходные значения аудита

Когда `AuditConfig.IncludeRawValues = true`, записи аудита включают исходные значения перехваченных атрибутов/URL. Эти значения проходят через `sanitizeRawValue`, выполняющий HTML-экранирование (`&` `<` `>` `"` `'`), что предотвращает хранимый XSS при отображении журнала аудита в браузере или на дашборде SIEM.

## Система аудита

События безопасности записываются через систему аудита, поддерживающую 8 типов событий, несколько встроенных Sink-ов и фильтрацию по уровням. Полная конфигурация описана в [Система аудита на практике](./audit-pipeline).

| Событие | Описание |
|---------|----------|
| `AuditEventBlockedTag` | Заблокированные HTML-теги |
| `AuditEventBlockedAttr` | Заблокированные атрибуты |
| `AuditEventBlockedURL` | Заблокированные URL |
| `AuditEventInputViolation` | Нарушения размера ввода |
| `AuditEventDepthViolation` | Нарушения глубины DOM |
| `AuditEventPathTraversal` | Попытки обхода пути (включая выход за AllowedBaseDir) |
| `AuditEventTimeout` | Тайм-ауты обработки |
| `AuditEventEncodingIssue` | Аномалии кодировки |

## Таблица решений по конфигурации безопасности

| Сценарий | Рекомендуемая конфигурация | Описание |
|----------|---------------------------|----------|
| Полностью доверенные внутренние данные | `DefaultConfig()` + опционально `EnableSanitization = false` | Приоритет производительности; отключайте санирование только при подтверждённом отсутствии внешнего ввода |
| HTML от пользовательских загрузок | `HighSecurityConfig()` | Полная защита: ужесточённые ограничения + полный аудит |
| Обработка внешних веб-страниц | `DefaultConfig()` | Санирование по умолчанию покрывает распространённые угрозы |
| Обработка пользовательских файловых путей | Установить `AllowedBaseDir` | Включить песочницу через дескриптор ОС для защиты от symlink/junction-эскалации |
| Высоконагруженный краулер | Уменьшить `MaxInputSize` + сократить `ProcessingTimeout` | Защита от злонамеренных страниц, замедляющих Worker-ы |

## Конфигурация высокой безопасности

`HighSecurityConfig()` — это предустановка, которая ужесточает все ограничения и включает полный аудит одним вызовом:

```go
cfg := html.HighSecurityConfig()
// Автоматически устанавливает:
//   MaxInputSize      = 10 МБ (по умолчанию 50 МБ)
//   MaxDepth          = 100 (по умолчанию 500)
//   ProcessingTimeout = 10 с (по умолчанию 30 с)
//   WorkerPoolSize    = 2 (по умолчанию 4)
//   Audit             = HighSecurityAuditConfig() (включён + с исходными значениями)
```

## Обработка ошибок

Все нарушения безопасности возвращают чёткие сигнальные ошибки, поддерживающие определение категории через `errors.Is`:

```go
package main

import (
	"errors"
	"fmt"

	"github.com/cybergodev/html"
)

func main() {
	data := []byte(`<html><body>Злонамеренная сверхглубокая вложенность</body></html>`)
	_, err := html.Extract(data)
	if err != nil {
		switch {
		case errors.Is(err, html.ErrInputTooLarge):
			// Ввод превышает лимит, записать и отклонить
			fmt.Println("Ввод слишком большой")
		case errors.Is(err, html.ErrMaxDepthExceeded):
			// Возможно рекурсивная бомба
			fmt.Println("Нарушение глубины")
		case errors.Is(err, html.ErrInternalPanic):
			// Panic восстановлен, следует исследовать ввод и сообщить
			fmt.Println("Внутренняя паника восстановлена")
		}
	}
	// Вывод: Нарушение глубины (пример, фактический результат зависит от ввода)
}
```

:::tip Подсказка
Для файловых ошибок используйте SafePath. Для `*FileError` используйте `SafePath()` для получения маскированной строки пути, вместо прямой печати исходной error — чтобы избежать утечки разрешённого реального пути в журналы.
:::

## Следующие шаги

- [Система аудита на практике](./audit-pipeline) — 8 типов событий, сравнение встроенных Sink-ов, конвейер маршрутизации по уровням
- [Контрольный список для production](./production-checklist) — предзапусковый чек-лист безопасности
- [Справочник API: система аудита](../../api-reference/modules/audit) — полные сигнатуры API
