feat(mera/b2c): публичный периметр на отдельном домене meraocenka.ru — этап 1 из 8 #2545

Merged
lekss361 merged 4 commits from feat/mera-b2c-perimeter into main 2026-08-02 12:51:47 +00:00
Owner

НЕ МЕРЖИТЬ до появления DNS-записей

Требуются A-записи → IP VPS 46.173.16.127 для трёх доменов: meraocenka.ru (канонический), merahome.ru и meraotsenka.ru (301 на канонический).

Что будет без них: конфиг синтаксически валиден, перезагрузка Caddy не падает, остальные домены не страдают (автоматический HTTPS изолирован по хостам). Но выпуск сертификата провалится и будет ретраиться с задержкой, а DNS-зависимые смоук-проверки закономерно покраснеют.


Обновление 2026-07-31 — смена домена и починка протухшего инварианта

1. Домен изменён с mera.gendsgn.ru на meraocenka.ru. Поддомен отвергнут: Яндекс считает поддомен отдельным сайтом (отдельная верификация в Вебмастере, отдельное назначение региона) — SEO-работы столько же, сколько у нового домена, но в имени остаётся чужой бренд gendsgn. Домены зарегистрированы 31.07.2026 (Beget, paid-till 2027-07-31).

2. Добавлены два site-блока с 301 на канонический — merahome.ru и meraotsenka.ru. Второй ловит альтернативный транслит «оценка» (ocenka/otsenka на слух неразличимы), первый — прежний рабочий вариант имени. {uri} сохраняет путь и query.

3. Починен протухший инвариант смоука — это важнее смены домена.

Проверка «gendsgn.ru/trade-in/v2 отдаёт 401 анониму» больше не верна: после #2555/#2558 trade-in ушёл с Caddy basic_auth на собственный логин. Проверено живьём:

https://gendsgn.ru/trade-in/v2      → 200
https://gendsgn.ru/trade-in/login   → 200

Страница рендерится анониму, а RouteGuard уводит на /login уже на клиенте. Гейт данных переехал на API. Проверка заменена на API-уровень, тоже проверено живьём:

/trade-in/api/v1/me           → 401
/trade-in/api/v1/history      → 401
/trade-in/api/v1/admin/users  → 401
/api/v1/admin/users           → 401

Если бы смоук поехал в прежнем виде, он был бы красным на ровном месте — а после «починки» под 200 стал бы зелёным при полностью открытом наружу бэкенде.

4. Влит main (ветка отставала на 3 дня, PR был mergeable: false). Ребейз не использовался — он потребовал бы force-push. Конфликт был один, в RouteGuard.tsx: main добавил if (isLoginPage), ветка — bypass публичных путей. Разрешён сохранением обоих, отдельными условиями.

Прогон смоука сейчас (4 инварианта безопасности зелёные, DNS-зависимые красные ожидаемо):

FAIL: meraocenka.ru root — public 200            (000 — DNS ещё нет)
FAIL: meraocenka.ru/v2 — B2B path must 404       (000 — DNS ещё нет)
PASS: trade-in /api/v1/me — 401 anonymous
PASS: trade-in /api/v1/history — 401 anonymous
PASS: trade-in /api/v1/admin/* — 401 anonymous
PASS: gendsgn.ru/api/v1/admin/* — 401 anonymous
FAIL: merahome.ru — 301 to canonical             (000 — DNS ещё нет)
FAIL: meraotsenka.ru — 301 to canonical          (000 — DNS ещё нет)

tsc --noEmit — чисто. Скобки Caddyfile сбалансированы (52/52). bash -n смоука — чисто.


Зачем этот этап

Первый из восьми в плане запуска B2C. Открывает периметр без функционала — публичной страницы-заглушки достаточно, чтобы доказать главное: B2B не приоткрылся. API не трогается вообще.

Порядок в плане жёсткий: периметр открывается последним по функционалу, но первым по инфраструктуре — чтобы всё остальное строилось за уже проверенной границей.

Почему отдельный домен, а не путь в текущем

Вся защита сегодня — это один блок route {}, где импорт basic_auth закрывает всё, что ниже по файлу, а публичность делается исключениями выше. Модель «запрещено всё, кроме дырок», порядко-зависимая — сам файл предупреждает об этом в шапке. Каждая новая дырка это шанс ошибиться матчером и открыть B2B-дашборд вместе с историей чужих оценок.

На отдельном site-блоке B2B-маршрутов физически нет: basic_auth не импортирован, разрешены ровно два маршрута, всё остальное — 404 через финальный catch-all. Открывать нечего.

Честное ограничение изоляции

Изоляция получилась на уровне маршрутизации, а не бандла. Next.js App Router не даёт независимый корневой layout без переноса всех существующих маршрутов в отдельную группу — это слишком крупный рефактор для первого этапа. Поэтому:

  • корневой layout по-прежнему оборачивает всё дерево, публичная страница исключена обходом в RouteGuard (тот же приём, что уже используется для превью, но безусловный — страница публична по продуктовому решению);
  • сама страница — серверный компонент, без "use client", без единого хука (useMe/useQuota/useHistory не импортируются), провайдер чата не подключён;
  • код B2B-компонентов лежит в том же бандле. Новой утечки это не создаёт: статика trade-in и так публична на основном домене, но и заявленной «физической изоляции» из плана здесь нет.

Полная изоляция бандла — отдельная задача, если решим, что она нужна.

Смоук периметра — главный артефакт этапа

Остаётся навсегда как регресс-тест. Запускается вручную и ежедневно по расписанию, а не как пре-мерж гейт: проверки требуют живого DNS и выпущенного сертификата, чего в контексте PR быть не может.

Побочный эффект мержа

PR трогает и корневой Caddyfile, и фронтенд trade-in, поэтому сработают оба деплой-пайплайна. Оба идемпотентны, чекаут общий, порядок не важен.

Test plan

  • Скобки конфига сбалансированы, синтаксис сверен с прецедентами в том же файле
  • tsc --noEmit — чисто
  • bash -n смоук-скрипта — чисто
  • Инварианты 3-4 (закрытость B2B) проверены живьём на проде — 401
  • Влит актуальный main, конфликт разрешён
  • Реальная проверка периметра — только после DNS и деплоя
  • caddy fmt локально не прогнать (Docker не запущен)
## ⛔ НЕ МЕРЖИТЬ до появления DNS-записей Требуются **A-записи → IP VPS `46.173.16.127`** для трёх доменов: `meraocenka.ru` (канонический), `merahome.ru` и `meraotsenka.ru` (301 на канонический). Что будет без них: конфиг синтаксически валиден, перезагрузка Caddy не падает, остальные домены не страдают (автоматический HTTPS изолирован по хостам). Но выпуск сертификата провалится и будет ретраиться с задержкой, а DNS-зависимые смоук-проверки закономерно покраснеют. --- ## Обновление 2026-07-31 — смена домена и починка протухшего инварианта **1. Домен изменён с `mera.gendsgn.ru` на `meraocenka.ru`.** Поддомен отвергнут: Яндекс считает поддомен отдельным сайтом (отдельная верификация в Вебмастере, отдельное назначение региона) — SEO-работы столько же, сколько у нового домена, но в имени остаётся чужой бренд `gendsgn`. Домены зарегистрированы 31.07.2026 (Beget, `paid-till 2027-07-31`). **2. Добавлены два site-блока с 301** на канонический — `merahome.ru` и `meraotsenka.ru`. Второй ловит альтернативный транслит «оценка» (`ocenka`/`otsenka` на слух неразличимы), первый — прежний рабочий вариант имени. `{uri}` сохраняет путь и query. **3. Починен протухший инвариант смоука — это важнее смены домена.** Проверка «`gendsgn.ru/trade-in/v2` отдаёт 401 анониму» **больше не верна**: после #2555/#2558 trade-in ушёл с Caddy basic_auth на собственный логин. Проверено живьём: ``` https://gendsgn.ru/trade-in/v2 → 200 https://gendsgn.ru/trade-in/login → 200 ``` Страница рендерится анониму, а `RouteGuard` уводит на `/login` уже на клиенте. Гейт данных переехал на API. Проверка заменена на API-уровень, тоже проверено живьём: ``` /trade-in/api/v1/me → 401 /trade-in/api/v1/history → 401 /trade-in/api/v1/admin/users → 401 /api/v1/admin/users → 401 ``` Если бы смоук поехал в прежнем виде, он был бы **красным на ровном месте** — а после «починки» под 200 стал бы зелёным при полностью открытом наружу бэкенде. **4. Влит `main`** (ветка отставала на 3 дня, PR был `mergeable: false`). Ребейз не использовался — он потребовал бы force-push. Конфликт был один, в `RouteGuard.tsx`: `main` добавил `if (isLoginPage)`, ветка — bypass публичных путей. Разрешён сохранением обоих, отдельными условиями. **Прогон смоука сейчас** (4 инварианта безопасности зелёные, DNS-зависимые красные ожидаемо): ``` FAIL: meraocenka.ru root — public 200 (000 — DNS ещё нет) FAIL: meraocenka.ru/v2 — B2B path must 404 (000 — DNS ещё нет) PASS: trade-in /api/v1/me — 401 anonymous PASS: trade-in /api/v1/history — 401 anonymous PASS: trade-in /api/v1/admin/* — 401 anonymous PASS: gendsgn.ru/api/v1/admin/* — 401 anonymous FAIL: merahome.ru — 301 to canonical (000 — DNS ещё нет) FAIL: meraotsenka.ru — 301 to canonical (000 — DNS ещё нет) ``` `tsc --noEmit` — чисто. Скобки `Caddyfile` сбалансированы (52/52). `bash -n` смоука — чисто. --- ## Зачем этот этап Первый из восьми в плане запуска B2C. Открывает **периметр без функционала** — публичной страницы-заглушки достаточно, чтобы доказать главное: B2B не приоткрылся. API не трогается вообще. Порядок в плане жёсткий: периметр открывается последним по функционалу, но первым по инфраструктуре — чтобы всё остальное строилось за уже проверенной границей. ## Почему отдельный домен, а не путь в текущем Вся защита сегодня — это **один блок `route {}`**, где импорт basic_auth закрывает всё, что ниже по файлу, а публичность делается исключениями выше. Модель «запрещено всё, кроме дырок», порядко-зависимая — сам файл предупреждает об этом в шапке. Каждая новая дырка это шанс ошибиться матчером и открыть B2B-дашборд вместе с историей чужих оценок. На отдельном site-блоке B2B-маршрутов **физически нет**: `basic_auth` не импортирован, разрешены ровно два маршрута, всё остальное — `404` через финальный catch-all. Открывать нечего. ## Честное ограничение изоляции Изоляция получилась **на уровне маршрутизации, а не бандла**. Next.js App Router не даёт независимый корневой layout без переноса всех существующих маршрутов в отдельную группу — это слишком крупный рефактор для первого этапа. Поэтому: - корневой layout по-прежнему оборачивает всё дерево, публичная страница исключена обходом в `RouteGuard` (тот же приём, что уже используется для превью, но безусловный — страница публична по продуктовому решению); - сама страница — серверный компонент, без `"use client"`, без единого хука (`useMe`/`useQuota`/`useHistory` не импортируются), провайдер чата не подключён; - код B2B-компонентов лежит в том же бандле. Новой утечки это не создаёт: статика trade-in и так публична на основном домене, но и заявленной «физической изоляции» из плана здесь нет. Полная изоляция бандла — отдельная задача, если решим, что она нужна. ## Смоук периметра — главный артефакт этапа Остаётся навсегда как регресс-тест. Запускается вручную и ежедневно по расписанию, а не как пре-мерж гейт: проверки требуют живого DNS и выпущенного сертификата, чего в контексте PR быть не может. ## Побочный эффект мержа PR трогает и корневой `Caddyfile`, и фронтенд trade-in, поэтому сработают оба деплой-пайплайна. Оба идемпотентны, чекаут общий, порядок не важен. ## Test plan - [x] Скобки конфига сбалансированы, синтаксис сверен с прецедентами в том же файле - [x] `tsc --noEmit` — чисто - [x] `bash -n` смоук-скрипта — чисто - [x] Инварианты 3-4 (закрытость B2B) проверены живьём на проде — 401 - [x] Влит актуальный `main`, конфликт разрешён - [ ] Реальная проверка периметра — только после DNS и деплоя - [ ] `caddy fmt` локально не прогнать (Docker не запущен)
lekss361 added 1 commit 2026-07-28 12:03:21 +00:00
feat(mera): открыть публичный B2C-периметр МЕРА (ЭТАП 1, без функционала)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 12s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m17s
2b86e94c5b
Отдельный поддомен mera.gendsgn.ru вместо дырки в auth-gate gendsgn.ru — на
новом site-блоке B2B-маршруты (v2, admin, scrapers, /trade-in/api/*) физически
не объявлены, basic_auth не импортируется вообще. Allowlist-by-default: только
корень "/" (rewrite на /trade-in/mera-public) + статика Next _next/*, всё
остальное — 404.

Публичная страница-заглушка (tradein-mvp/frontend/src/app/mera-public/) не
тянет RouteGuard/SupportChatProvider функционально — RouteGuard получил
безусловный bypass для этого пути (аналог существующего /ui-preview,
но always-on, не за dev-флагом); сама страница — server component без
useMe/useQuota/useHistory. robots: noindex на этом этапе.

CI-регресс на периметр: scripts/smoke-mera-perimeter.sh (4 проверки: публичный
поддомен 200, B2B-путь на нём 404, gendsgn.ru/trade-in/v2 и /api/v1/admin/*
по-прежнему 401 анониму) + .forgejo/workflows/perimeter-smoke.yml
(workflow_dispatch + daily cron — не pre-merge гейт, требует реального DNS).

Требуется DNS: A-record mera.gendsgn.ru → IP VPS. До появления записи ACME
для нового хоста будет ретраить с backoff, остальные site-блоки не страдают
(автоматический HTTPS в Caddy изолирован per-hostname).
bot-backend added 1 commit 2026-07-31 20:59:07 +00:00
Merge remote-tracking branch 'refs/remotes/forgejo/main' into feat/mera-b2c-perimeter
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m16s
536640a252
# Conflicts:
#	tradein-mvp/frontend/src/components/auth/RouteGuard.tsx
lekss361 changed title from feat(mera/b2c): публичный периметр на отдельном поддомене — этап 1 из 8 to feat(mera/b2c): публичный периметр на отдельном домене meraocenka.ru — этап 1 из 8 2026-07-31 21:00:11 +00:00
bot-backend added 1 commit 2026-08-02 12:12:35 +00:00
Merge branch 'main' into feat/mera-b2c-perimeter
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
537b0ac06e
Конфликты (add/add + content) разрешены в пользу main: лэндинг #2615 и рефакторинг RouteGuard (вынос GuardedRoute в отдельный чанк, хелпер isPublicPath) уже содержат всё, что добавляла заглушка этой ветки.

Плюс закрыт разрыв на стыке #2615 и этого PR: футер лэндинга ссылается на политику ПДн через next/link, Next с basePath эмитит её как /trade-in/mera-public/privacy — такого handle в блоке периметра не было, ссылка уходила в catch-all 404. Добавлен узкий handle ровно на поддерево лэндинга (НЕ /trade-in/*) + два регресс-теста в смоук на то, что matcher не шире задуманного.
bot-backend added 1 commit 2026-08-02 12:22:23 +00:00
fix(mera/b2c): ужесточить периметр по итогам ревью — _next/static вместо _next, скраб X-Authenticated-User
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 7s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
c8ff201345
1. Матчер статики сужен с /trade-in/_next/* до /trade-in/_next/static/* — тот
же, что у @uipreview (Caddyfile:78), проверенный в проде. Широкий вариант
открывал анонимам /_next/image (оптимизатор картинок, CPU по запросу), который
лэндингу не нужен: next/image, <img> и url() в mera-public не встречаются
вообще, картинок нет, шрифты — next/font/google self-hosted в _next/static/media.

2. Во все три reverse_proxy блока добавлен header_up -X-Authenticated-User —
единообразие с остальным периметром (:87, :129, :169). Блок не под basic_auth,
т.е. анонимный клиент тем более может прислать свой заголовок. Сейчас инертно
(лэндинг статичен), но станет дырой на этапе 5, когда откроется публичный
/estimate.

3. Регресс-тест на сужение: /trade-in/_next/image должен отдавать 404.
Author
Owner

Блокер снят — DNS есть, мержим

Шапка PR (« НЕ МЕРЖИТЬ до появления DNS-записей») устарела. Проверено сейчас, резолв через 8.8.8.8:

meraocenka.ru    A → 46.173.16.127
merahome.ru      A → 46.173.16.127
meraotsenka.ru   A → 46.173.16.127

Текущее состояние доменов до мержа — они уже смотрят в прод, но site-блока нет, поэтому:

  • https://meraocenka.ru/ → TLS handshake fails (сертификат не выпущен, блока нет). Контроль: https://gendsgn.ru/ с той же машины в тот же момент → 200/401, т.е. это не локальная проблема.
  • http://meraocenka.ru/401 — запрос проваливается в дефолтную обработку и получает basic_auth от B2B-контура.

То есть посетитель публичного домена сейчас видит либо ошибку сертификата, либо окно логина. Это и чинится мержем.

Что изменено в этом заходе

1. Влит актуальный main (ветка отставала на 37 коммитов). Конфликты — три файла:

  • app/mera-public/layout.tsx, page.tsx (add/add): разрешены в пользу main. #2615 заменил заглушку этой ветки на полноценный лэндинг с компонентами;
  • RouteGuard.tsx (content): в пользу main — там уже есть вынос GuardedRoute в отдельный чанк и хелпер isPublicPath(), константы BASE_PATH/ENABLE_PREVIEW из старой стороны больше не нужны (логика уехала в GuardedRoute). Проверено грепом, что висячих ссылок на них в файле не осталось.

Дифф ветки к main после мержа — ровно вклад периметра: 3 файла, +226/-0.

2. Закрыт разрыв на стыке #2615 и этого PR. Периметр писался под заглушку без подстраниц, а у лэндинга есть privacy/. Футер ссылается на неё через next/link (PRIVACY_PATH = "/mera-public/privacy"), Next с basePath=/trade-in эмитит /trade-in/mera-public/privacy — такого handle в блоке не было, ссылка уходила в catch-all 404. То есть обязательный по 152-ФЗ документ был бы недоступен с публичной страницы с первой же секунды после выпуска сертификата.

Добавлен handle /trade-in/mera-public/*ровно поддерево лэндинга, не /trade-in/*. Плюс два регресс-теста в смоук именно на ширину этого матчера: meraocenka.ru/trade-in/v2 и /trade-in/api/v1/me обязаны отдавать 404 (старая проверка /v2 без префикса такое расширение бы не заметила).

3. Ужесточения по итогам ревью (вердикт ⚠️ MINOR, критичных нет, утечки B2B не найдено):

  • матчер статики сужен /trade-in/_next/*/trade-in/_next/static/* — тот же, что у @uipreview (:78), проверенный в проде. Широкий вариант открывал анонимам /_next/image (оптимизация картинок = CPU по запросу), который лэндингу не нужен: проверено, next/image, <img> и url() в mera-public/ не встречаются вообще, картинок нет, шрифты — next/font/google self-hosted в _next/static/media;
  • во все три reverse_proxy добавлен header_up -X-Authenticated-User — единообразие с остальным периметром (:87, :129, :169). Блок не под basic_auth, значит аноним тем более может прислать свой заголовок. Сейчас инертно (лэндинг статичен), но станет дырой на этапе 5, когда откроется публичный /estimate;
  • регресс-тест на сужение: /trade-in/_next/image → 404.

Проверено

  • tsc --noEmit в tradein-mvp/frontend — exit 0
  • bash -n смоука — чисто
  • скобки Caddyfile — 68/68
  • CI по head c8ff2013 — combined state success (тестовые джобы skipped по paths-filter: в диффе нет backend/frontend-кода)
  • ревью периметра отдельным агентом: handle / — точное совпадение, не префикс; Caddy нормализует путь до матчинга (.., %2e%2e, двойные слэши allowlist не обходят); handle сортируются по специфичности, catch-all не перехватывает разрешённые пути; rewrite без интерполяции клиентского пути; сателлитные 301 без open-redirect (хост захардкожен, из клиента только {uri})

Не проверено

  • Живой периметр — только после деплоя и выпуска сертификата. Смоук прогоняю сразу после.
  • caddy fmt локально не гонял (Docker не поднят).
## Блокер снят — DNS есть, мержим Шапка PR («⛔ НЕ МЕРЖИТЬ до появления DNS-записей») устарела. Проверено сейчас, резолв через 8.8.8.8: ``` meraocenka.ru A → 46.173.16.127 merahome.ru A → 46.173.16.127 meraotsenka.ru A → 46.173.16.127 ``` Текущее состояние доменов до мержа — они уже смотрят в прод, но site-блока нет, поэтому: - `https://meraocenka.ru/` → TLS handshake fails (сертификат не выпущен, блока нет). Контроль: `https://gendsgn.ru/` с той же машины в тот же момент → 200/401, т.е. это не локальная проблема. - `http://meraocenka.ru/` → **401** — запрос проваливается в дефолтную обработку и получает basic_auth от B2B-контура. То есть посетитель публичного домена сейчас видит либо ошибку сертификата, либо окно логина. Это и чинится мержем. ## Что изменено в этом заходе **1. Влит актуальный `main`** (ветка отставала на 37 коммитов). Конфликты — три файла: - `app/mera-public/layout.tsx`, `page.tsx` (add/add): разрешены в пользу `main`. #2615 заменил заглушку этой ветки на полноценный лэндинг с компонентами; - `RouteGuard.tsx` (content): в пользу `main` — там уже есть вынос `GuardedRoute` в отдельный чанк и хелпер `isPublicPath()`, константы `BASE_PATH`/`ENABLE_PREVIEW` из старой стороны больше не нужны (логика уехала в `GuardedRoute`). Проверено грепом, что висячих ссылок на них в файле не осталось. Дифф ветки к `main` после мержа — ровно вклад периметра: 3 файла, +226/-0. **2. Закрыт разрыв на стыке #2615 и этого PR.** Периметр писался под заглушку без подстраниц, а у лэндинга есть `privacy/`. Футер ссылается на неё через `next/link` (`PRIVACY_PATH = "/mera-public/privacy"`), Next с `basePath=/trade-in` эмитит `/trade-in/mera-public/privacy` — такого `handle` в блоке не было, ссылка уходила в catch-all `404`. То есть обязательный по 152-ФЗ документ был бы недоступен с публичной страницы с первой же секунды после выпуска сертификата. Добавлен `handle /trade-in/mera-public/*` — **ровно поддерево лэндинга, не `/trade-in/*`**. Плюс два регресс-теста в смоук именно на ширину этого матчера: `meraocenka.ru/trade-in/v2` и `/trade-in/api/v1/me` обязаны отдавать 404 (старая проверка `/v2` без префикса такое расширение бы не заметила). **3. Ужесточения по итогам ревью** (вердикт ⚠️ MINOR, критичных нет, утечки B2B не найдено): - матчер статики сужен `/trade-in/_next/*` → `/trade-in/_next/static/*` — тот же, что у `@uipreview` (:78), проверенный в проде. Широкий вариант открывал анонимам `/_next/image` (оптимизация картинок = CPU по запросу), который лэндингу не нужен: проверено, `next/image`, `<img>` и `url()` в `mera-public/` не встречаются вообще, картинок нет, шрифты — `next/font/google` self-hosted в `_next/static/media`; - во все три `reverse_proxy` добавлен `header_up -X-Authenticated-User` — единообразие с остальным периметром (:87, :129, :169). Блок не под `basic_auth`, значит аноним тем более может прислать свой заголовок. Сейчас инертно (лэндинг статичен), но станет дырой на этапе 5, когда откроется публичный `/estimate`; - регресс-тест на сужение: `/trade-in/_next/image` → 404. ## Проверено - `tsc --noEmit` в `tradein-mvp/frontend` — exit 0 - `bash -n` смоука — чисто - скобки `Caddyfile` — 68/68 - CI по head `c8ff2013` — combined state **success** (тестовые джобы `skipped` по paths-filter: в диффе нет backend/frontend-кода) - ревью периметра отдельным агентом: `handle /` — точное совпадение, не префикс; Caddy нормализует путь до матчинга (`..`, `%2e%2e`, двойные слэши allowlist не обходят); `handle` сортируются по специфичности, catch-all не перехватывает разрешённые пути; `rewrite` без интерполяции клиентского пути; сателлитные 301 без open-redirect (хост захардкожен, из клиента только `{uri}`) ## Не проверено - Живой периметр — только после деплоя и выпуска сертификата. Смоук прогоняю сразу после. - `caddy fmt` локально не гонял (Docker не поднят).
lekss361 merged commit 55254b43e4 into main 2026-08-02 12:51:47 +00:00
lekss361 deleted branch feat/mera-b2c-perimeter 2026-08-02 12:51:47 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2545
No description provided.