From c93c6adc7a7e027ca8b8fc883a51d40bf62e603e Mon Sep 17 00:00:00 2001 From: IK Date: Sun, 13 Sep 2026 09:33:39 +0700 Subject: [PATCH] security: restrict production content origins --- README.md | 2 ++ deploy/Caddyfile | 2 +- docs/ROADMAP.md | 2 +- docs/csp-inventory.md | 21 +++++++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 docs/csp-inventory.md diff --git a/README.md b/README.md index 45cf3e0..684df91 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ FastAPI ─ PostgreSQL 17 Наружу production-профиль публикует только Caddy. PostgreSQL, API, Astro и MinIO находятся в Docker-сетях. Caddy завершает TLS и защищает административные страницы Basic Auth; административный API отдельно проверяет Bearer-токен в FastAPI. Basic не накладывается на API-запросы. +Production CSP ограничивает browser-запросы текущим доменом и отдельным files-доменом для изображений, запрещает plugins, frames, inline handlers, eval, wildcard и HTTP. Оставшиеся inline script/style зависимости и путь к nonce/hash перечислены в [CSP inventory](docs/csp-inventory.md). + Gitea Actions workflow `.gitea/workflows/ci.yml` на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты. Актуальная инвентаризация источников и правила подключения адаптеров находятся в [`docs/data-source-audit.md`](docs/data-source-audit.md). Разрешённый технический пилот RF4DB/RF4-STAT описан в [`docs/community-source-pilot.md`](docs/community-source-pilot.md), а статус разрешений и лимитов — в [`docs/data-permissions.md`](docs/data-permissions.md). Все наблюдения сначала попадают в staging. Полные записи с уже подтверждёнными алиасами могут публиковаться автоматически; новые соответствия требуют модерации, а неполные записи показываются отдельно и не влияют на индекс. diff --git a/deploy/Caddyfile b/deploy/Caddyfile index 4307450..ed6da03 100644 --- a/deploy/Caddyfile +++ b/deploy/Caddyfile @@ -52,7 +52,7 @@ Permissions-Policy "camera=(), microphone=(), geolocation=()" X-Frame-Options "DENY" Cross-Origin-Opener-Policy "same-origin" - Content-Security-Policy "frame-ancestors 'none'; base-uri 'self'; object-src 'none'" + Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; connect-src 'self'; img-src 'self' data: https://{$FILES_DOMAIN}; font-src 'self'; media-src 'self'; manifest-src 'self'; script-src 'self' 'unsafe-inline'; script-src-attr 'none'; style-src 'self' 'unsafe-inline'; style-src-attr 'unsafe-inline'; upgrade-insecure-requests" } } diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d9e750a..46b55c5 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -54,7 +54,7 @@ - [ ] **Q11 · Декомпозиция API — в работе.** После фиксации OpenAPI публичные catalog, activity/spots и records/community/status/import-history вынесены в отдельные `APIRouter`; submission endpoints уже принадлежат собственному router, а их security-слой изолирован в `submission_security`. URL, response models и generated contract сохранены. Далее: физически вынести submission handlers из `main.py`, затем admin. - [ ] **Q12 · Query-plan gate.** В рамках Q07 снять `EXPLAIN (ANALYZE, BUFFERS)` для activity, records, spot detail и public spot pages на реалистичном наборе данных. Существующие индексы миграции `0011_query_indexes` не дублировать; индекс с `fish_id`, SQL-агрегацию или materialized view добавлять только по измеренному плану и p95. - [x] **Q13 · Production bootstrap в CI.** Отдельный workflow запускает `deploy/test-production-bootstrap.sh` вручную или раз в неделю, а не на каждом push. Вывод bootstrap всегда сохраняется 14 дней; при падении добавляются Compose status и Playwright diagnostics. -- [ ] **Q14 · Полная CSP.** Расширить текущую CSP (`frame-ancestors`, `base-uri`, `object-src`) до `default-src`, `script-src`, `style-src`, `img-src`, `connect-src` и `form-action`. Сначала инвентаризировать inline scripts/styles Astro, затем внедрить nonce/hash или безопасное вынесение; проверить report/admin/OG без ослабления до произвольных внешних origin. +- [ ] **Q14 · Полная CSP — origin-policy внедрена.** Production ограничивает default/connect/form/font/media/manifest текущим доменом, изображения — self/data/`FILES_DOMAIN`, запрещает inline event handlers, eval, wildcard и HTTP. Инвентаризация зафиксировала динамический JSON-LD, page scripts, scoped styles и CSS variables; из-за них `unsafe-inline` временно остаётся только для script/style элементов и style attributes. Далее вынести page scripts, решить nonce/hash JSON-LD и убрать исключения поэтапно с bootstrap-проверкой report/admin/OG/screenshots. - [x] **Q15 · Частичная деградация главной.** SSR независимо получает activity, community signals и оба справочника через settled-результаты. Отказ секции показывает собственный `StatePanel`, сохраняет остальные данные и HTTP 200 с `X-RF4-Partial`/`Cache-Control: no-store`; только отказ всех четырёх частей возвращает 503, `Retry-After` и noindex. Client-side loading и optimistic UI не добавлялись. - [x] **Q16 · Контракт OpenAPI.** `apps/api/openapi.json` детерминированно генерируется из FastAPI; CI проверяет его актуальность после backend suite. Изменение artifact обязательно рассматривается вместе с реализацией, а ручное редактирование не используется. - [x] **Q17 · Эксплуатационные документы.** Зафиксированы ADR по Astro/FastAPI/PostgreSQL, локальному cache, scheduler/cooldown и разделению PostgreSQL/MinIO. Incident runbook покрывает заполнение диска, отказ PostgreSQL/MinIO, зависшие импорты, ошибки миграций, компрометацию секретов и критерии закрытия без опасных reset/recreate операций. diff --git a/docs/csp-inventory.md b/docs/csp-inventory.md new file mode 100644 index 0000000..84f11e2 --- /dev/null +++ b/docs/csp-inventory.md @@ -0,0 +1,21 @@ +# CSP inventory + +Проверено 13 сентября 2026 года. Production не требует произвольных внешних origin: browser API-запросы идут на текущий домен, изображения скриншотов — только на `FILES_DOMAIN`, шрифты и сборка Astro — локальные. + +## Временные inline-зависимости + +- динамический JSON-LD в `Layout.astro`; +- inline bootstrap в `report.astro` и `spots/[id].astro`; +- page scripts главной и трёх admin-экранов; +- scoped `