(medium) RouteGuard.tsx/RouteGuard: analyst на запрещённом /admin/* молча редиректится в /trade-in/ вместо NoAccessScreen #1439

Closed
opened 2026-06-15 05:55:36 +00:00 by bot-backend · 1 comment
Collaborator

Severity: medium · Категория: logic · Файл: frontend/src/components/auth/RouteGuard.tsx:63-79

RouteGuard.tsx:63-79. Блок авто-редиректа в /trade-in/ (введён для pilot-only юзеров, #799) проверяет только два условия: (1) текущий путь не под /trade-in и (2) роли доступен /trade-in/. Он НЕ проверяет, что trade-in — ЕДИНСТВЕННАЯ доступная роли зона. Подтверждено по auth/roles.yaml: роль analyst имеет paths=["/"], deny=["/admin/","/api/v1/admin/","/trade-in/api/v1/admin/"]. Трассировка для analyst, заходящего прямым URL на /admin/* (напр. /admin/users): внешний isPathAllowed(allowed_paths=["/"], deny=[...], "/admin/users") = false (deny "/admin/" матчит) → вход в блок; внутри isPathAllowed(["/"], deny, "/trade-in/") = true (паттерн / разрешает /trade-in/, ни один deny не трогает /trade-in/), pathname не под /trade-in → срабатывает window.location.replace("/trade-in/"). Импакт: analyst (и любая будущая multi-zone роль с доступом к /trade-in/ и частичными deny) при заходе на запрещённый /admin/* раздел вместо честного NoAccessScreen(variant="path") молча выкидывается в чужое приложение Trade-In (отдельный bundle за Caddy → полная перезагрузка), реальная причина отказа маскируется. Комментарий в коде (стр. 64-70) декларирует обратное («pilot-only юзер… admin / multi-scope сюда не попадают»), но утверждение про isPathAllowed=true относится к внешней проверке самого /admin-пути, которая для analyst как раз false. Severity medium, не high: это UX/маскировка, не дыра в безопасности — backend rbac_guard (app/main.py) и middleware всё равно жёстко 403-ят /api/v1/admin/* для role!=admin, данные не утекают. Чинить: добавить проверку, что trade-in — действительно единственная рабочая зона роли, например условие «текущий путь не разрешён И при этом роль НЕ имеет доступа к корню /»: заменить редирект-гейт на !isPathAllowed(allowed, deny, "/") && isPathAllowed(allowed, deny, "/trade-in/") (т.е. редиректить только если у роли нет доступа к главному фронту), иначе показывать NoAccessScreen(variant="path").

Почему баг: Комментарий в коде прямо декларирует обратное поведение: «pilot-only юзер (единственная доступная зона — trade-in)» и «admin / multi-scope с разрешённым / сюда не попадают». Но multi-scope роль (analyst) попадает: для analyst запрещён только /admin/, а /trade-in/ разрешён, поэтому при заходе на запрещённый /admin/* срабатывает редирект. Логика «единственная зона» нигде не выражена — нет проверки, что НЕ-trade-in пути недоступны. Backend roles.yaml подтверждает: analyst paths="/", значит ему доступны /trade-in/ и почти всё, кроме admin → условие редиректа для него истинно на admin-путях.

Как чинить: Редиректить в /trade-in/ только когда trade-in реально единственная рабочая зона. Например, ограничить ветку ролью pilot (data.role === "pilot") либо проверять, что корень и типичные не-trade-in пути недоступны (isPathAllowed(..., "/") === false). Для analyst/admin при заходе на запрещённый путь показывать NoAccessScreen(variant="path").


deep-audit «армия ботов 2» · verify+harden, confidence 0.9 · unit U89

**Severity:** medium · **Категория:** logic · **Файл:** `frontend/src/components/auth/RouteGuard.tsx:63-79` RouteGuard.tsx:63-79. Блок авто-редиректа в /trade-in/ (введён для pilot-only юзеров, #799) проверяет только два условия: (1) текущий путь не под /trade-in и (2) роли доступен /trade-in/. Он НЕ проверяет, что trade-in — ЕДИНСТВЕННАЯ доступная роли зона. Подтверждено по auth/roles.yaml: роль analyst имеет paths=["/**"], deny=["/admin/**","/api/v1/admin/**","/trade-in/api/v1/admin/**"]. Трассировка для analyst, заходящего прямым URL на /admin/* (напр. /admin/users): внешний isPathAllowed(allowed_paths=["/**"], deny=[...], "/admin/users") = false (deny "/admin/**" матчит) → вход в блок; внутри isPathAllowed(["/**"], deny, "/trade-in/") = true (паттерн /** разрешает /trade-in/, ни один deny не трогает /trade-in/), pathname не под /trade-in → срабатывает window.location.replace("/trade-in/"). Импакт: analyst (и любая будущая multi-zone роль с доступом к /trade-in/ и частичными deny) при заходе на запрещённый /admin/* раздел вместо честного NoAccessScreen(variant="path") молча выкидывается в чужое приложение Trade-In (отдельный bundle за Caddy → полная перезагрузка), реальная причина отказа маскируется. Комментарий в коде (стр. 64-70) декларирует обратное («pilot-only юзер… admin / multi-scope сюда не попадают»), но утверждение про isPathAllowed=true относится к внешней проверке самого /admin-пути, которая для analyst как раз false. Severity medium, не high: это UX/маскировка, не дыра в безопасности — backend rbac_guard (app/main.py) и middleware всё равно жёстко 403-ят /api/v1/admin/* для role!=admin, данные не утекают. Чинить: добавить проверку, что trade-in — действительно единственная рабочая зона роли, например условие «текущий путь не разрешён И при этом роль НЕ имеет доступа к корню /»: заменить редирект-гейт на `!isPathAllowed(allowed, deny, "/") && isPathAllowed(allowed, deny, "/trade-in/")` (т.е. редиректить только если у роли нет доступа к главному фронту), иначе показывать NoAccessScreen(variant="path"). **Почему баг:** Комментарий в коде прямо декларирует обратное поведение: «pilot-only юзер (единственная доступная зона — trade-in)» и «admin / multi-scope с разрешённым `/` сюда не попадают». Но multi-scope роль (analyst) попадает: для analyst запрещён только /admin/**, а /trade-in/ разрешён, поэтому при заходе на запрещённый /admin/* срабатывает редирект. Логика «единственная зона» нигде не выражена — нет проверки, что НЕ-trade-in пути недоступны. Backend roles.yaml подтверждает: analyst paths="/**", значит ему доступны /trade-in/ и почти всё, кроме admin → условие редиректа для него истинно на admin-путях. **Как чинить:** Редиректить в /trade-in/ только когда trade-in реально единственная рабочая зона. Например, ограничить ветку ролью pilot (data.role === "pilot") либо проверять, что корень и типичные не-trade-in пути недоступны (isPathAllowed(..., "/") === false). Для analyst/admin при заходе на запрещённый путь показывать NoAccessScreen(variant="path"). --- <sub>deep-audit «армия ботов 2» · verify+harden, confidence 0.9 · unit U89</sub>
bot-backend added the
week ревью 1
label 2026-06-15 05:55:36 +00:00
bot-backend added the
scope/frontend
label 2026-06-15 11:02:05 +00:00
Owner

🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)

Текущее состояние кода: frontend/src/components/auth/RouteGuard.tsx:63-80 (редирект-гейт на стр. 71-78); семантика glob — frontend/src/lib/isPathAllowed.ts:66-79 (deny overrides allow).

Корень: Условие редиректа !pathname.startsWith("/trade-in") && isPathAllowed(allowed, deny, "/trade-in/") (RouteGuard.tsx:72-74) проверяет только доступность trade-in, но НЕ то, что trade-in — единственная зона роли. Для analyst (paths=/**, deny=/admin/**) isPathAllowed(..., "/trade-in/") = true, поэтому при заходе на запрещённый /admin/* срабатывает window.location.replace("/trade-in/") (стр. 76) вместо NoAccessScreen variant="path" (стр. 79). Комментарий стр. 64-70 декларирует обратное. Безопасность не страдает — backend всё равно 403-ит /api/v1/admin/*; это UX/маскировка причины отказа.

Что править:

  • Добавить в гейт проверку, что у роли НЕТ доступа к корню главного фронта: !isPathAllowed(data.allowed_paths, data.deny_paths, "/") && isPathAllowed(..., "/trade-in/") — редиректить только pilot-only роль, у которой / недоступен.
  • Для analyst/admin на запрещённом пути — показывать NoAccessScreen variant="path".

Что посмотреть:

  • Код: RouteGuard.tsx:71-79 (условие редиректа), isPathAllowed.ts:66-79, auth/roles.yaml (роли analyst/pilot — источник allow/deny). Добавить unit-тест на analyst+/admin/users → NoAccessScreen, pilot+/ → redirect.
  • Vault: релевантных заметок не найдено (поиск «RouteGuard trade-in redirect pilot analyst» — пусто).
**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода:** `frontend/src/components/auth/RouteGuard.tsx:63-80` (редирект-гейт на стр. 71-78); семантика glob — `frontend/src/lib/isPathAllowed.ts:66-79` (deny overrides allow). **Корень:** Условие редиректа `!pathname.startsWith("/trade-in") && isPathAllowed(allowed, deny, "/trade-in/")` (RouteGuard.tsx:72-74) проверяет только доступность trade-in, но НЕ то, что trade-in — единственная зона роли. Для analyst (paths=`/**`, deny=`/admin/**`) `isPathAllowed(..., "/trade-in/")` = true, поэтому при заходе на запрещённый `/admin/*` срабатывает `window.location.replace("/trade-in/")` (стр. 76) вместо `NoAccessScreen variant="path"` (стр. 79). Комментарий стр. 64-70 декларирует обратное. Безопасность не страдает — backend всё равно 403-ит `/api/v1/admin/*`; это UX/маскировка причины отказа. **Что править:** - Добавить в гейт проверку, что у роли НЕТ доступа к корню главного фронта: `!isPathAllowed(data.allowed_paths, data.deny_paths, "/") && isPathAllowed(..., "/trade-in/")` — редиректить только pilot-only роль, у которой `/` недоступен. - Для analyst/admin на запрещённом пути — показывать `NoAccessScreen variant="path"`. **Что посмотреть:** - Код: `RouteGuard.tsx:71-79` (условие редиректа), `isPathAllowed.ts:66-79`, `auth/roles.yaml` (роли analyst/pilot — источник allow/deny). Добавить unit-тест на analyst+`/admin/users` → NoAccessScreen, pilot+`/` → redirect. - Vault: релевантных заметок не найдено (поиск «RouteGuard trade-in redirect pilot analyst» — пусто).
Sign in to join this conversation.
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#1439
No description provided.