66 lines
8.7 KiB
Markdown
66 lines
8.7 KiB
Markdown
# Март 2018 — безопасная загрузка файлов: draft-review
|
|
|
|
Статус: принято в публикационный слой 31 июля 2026 после независимого audit. Эта партия существует как три ревизии из `web/scripts/upgrade-2018-03.mjs --print-revisions`; слой `web/data/editorial-revisions.mjs` сопоставляет их только по стабильным slug, не меняя даты, автора или историю Git.
|
|
|
|
| Slug | Главный вопрос | Основной текст без источников |
|
|
| --- | --- | ---: |
|
|
| `editorial-2018-03-practice-safe-uploads` | Как принять JPEG или PNG для аватара без доверия к имени и MIME-типу формы? | 5 666 знаков |
|
|
| `editorial-2018-03-mechanism-safe-uploads` | Какие признаки файла можно использовать для какой проверки? | 6 209 знаков |
|
|
| `editorial-2018-03-field-safe-uploads` | Как выдать владельцу приватный PDF, если файл хранится вне веб-корня? | 5 688 знаков |
|
|
|
|
## 1. Факты и техника — пройдено
|
|
|
|
### Практика: приём аватара
|
|
|
|
- Проверены [коды ошибок загрузки PHP](https://www.php.net/manual/en/features.file-upload.errors.php), [move_uploaded_file](https://www.php.net/manual/en/function.move-uploaded-file.php), [finfo_file](https://www.php.net/manual/en/function.finfo-file.php), [ограничение getimagesize как валидатора](https://www.php.net/manual/en/function.getimagesize.php) и [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html).
|
|
- Пример проверяет `UPLOAD_ERR_OK`, прикладной лимит, MIME-тип через Fileinfo и размеры изображения до переноса. `getimagesize()` используется только для размеров, не как доказательство корректности изображения.
|
|
- Ограничения названы прямо: нет антивирусной проверки, CSRF-защиты и обработки миниатюр.
|
|
|
|
### Механизм: границы доверия
|
|
|
|
- Проверены [RFC 7578 для multipart/form-data](https://www.rfc-editor.org/rfc/rfc7578), [коды ошибок PHP](https://www.php.net/manual/en/features.file-upload.errors.php), [Fileinfo](https://www.php.net/manual/en/function.finfo-file.php), [getimagesize](https://www.php.net/manual/en/function.getimagesize.php) и [OWASP](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html).
|
|
- Воспроизводимый `curl`-пример не утверждает конкретный результат базы magic: он показывает разницу между заявленным клиентом `type` и типом, который определяет Fileinfo.
|
|
- Статья не называет Fileinfo антивирусом и не переносит ответственность за лимит всего запроса на одну PHP-функцию.
|
|
|
|
### Поле: выдача приватного PDF
|
|
|
|
- Проверены [OWASP для размещения файлов вне webroot](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html), [header()](https://www.php.net/manual/en/function.header.php), [readfile()](https://www.php.net/manual/en/function.readfile.php) и [RFC 6266 для Content-Disposition](https://www.rfc-editor.org/rfc/rfc6266).
|
|
- В коде ID документа и текущий пользователь участвуют в одном SQL-запросе; путь строится только из ключа, прошедшего контрактное регулярное выражение.
|
|
- Ограничения не скрыты: в примере нет Range, кеширования, ограничения частоты и эффективной выдачи больших файлов.
|
|
|
|
Версионная оговорка: код ориентирован на PHP 7.2. В статьях не используются приёмы, добавленные позднее; актуальные страницы PHP Manual взяты как первичный справочник функций и их ограничений.
|
|
|
|
## 2. Редактура и голос — пройдено после исправления стоп-условия
|
|
|
|
- Каждая статья отвечает на один вопрос и начинает с наблюдаемой ситуации, а не с общего рассуждения о безопасности.
|
|
- Для `mechanism` и `field` первоначальный строгий audit обнаружил недостаточно явную постановку проблемы. В первые два предложения добавлены формулировки `Симптом:` и `Цена ошибки`; сильная исходная подводка сохранена дальше в том же абзаце.
|
|
- Повторный CLI-audit подтвердил: обе статьи содержат симптом и цену ошибки в первых 420 знаках; длины — 6 209 и 5 688 знаков соответственно.
|
|
- Речь соответствует 2018 году: короткие технические абзацы, «давайте» и «я бы» только там, где автор делает практический вывод; нет обещаний универсального решения, поздних инструментов и шаблонных оборотов.
|
|
- Во всех трёх ревизиях есть проблема, таблица, воспроизводимый пример, порядок действий, ограничения, один рисунок и минимум четыре первичных или нормативных источника.
|
|
|
|
## 3. Визуал и выпуск — пройдено для черновика
|
|
|
|
- `xmllint --noout` прошёл для трёх SVG: `php-upload-avatar-contract.svg`, `php-upload-trust-signals.svg`, `php-private-download-flow.svg`.
|
|
- В локальном рендере SVG проверены title, доступное описание, границы текста и масштаб 1280×720: 31, 26 и 20 текстовых узлов соответственно; выходов за границы нет.
|
|
- На первой схеме во время визуальной проверки найден и исправлен контраст номеров этапов: цвет изменён с белого на тёмный `rgb(46, 82, 103)`.
|
|
- Каждый рисунок будет иметь осмысленный `alt` и подпись через данные ревизии. Таблицы обёрнуты в `table-scroll`; текущие стили блога добавляют горизонтальную прокрутку при минимальной ширине таблицы 620px.
|
|
- Production-сборка и проверка опубликованных URL не запускались намеренно: статьи ещё не интегрированы в `web/data/articles.json`. Это выпускной шаг основного агента, а не основание менять архив из этой ветки.
|
|
|
|
## Повторённые команды
|
|
|
|
```sh
|
|
node --check web/scripts/upgrade-2018-03.mjs
|
|
node web/scripts/upgrade-2018-03.mjs --print-revisions
|
|
xmllint --noout \
|
|
web/public/assets/editorial/2018/php-upload-avatar-contract.svg \
|
|
web/public/assets/editorial/2018/php-upload-trust-signals.svg \
|
|
web/public/assets/editorial/2018/php-private-download-flow.svg
|
|
```
|
|
|
|
Результат: три ревизии готовы для точечной интеграции без перезаписи остальных статей.
|
|
|
|
## Приёмка основного агента
|
|
|
|
- Повторно пройден строгий `audit-quality-batch.mjs`: 5 666 / 6 209 / 5 688 знаков основного текста; в каждой статье найдены рисунок с `alt`, таблица, код, порядок действий и отдельный раздел источников.
|
|
- Проверены безопасный import модуля и CLI-вывод ровно трёх ревизий. Production build после интеграции прошёл и сгенерировал 374 статические страницы.
|