security(routes): закрыт долг по незащищённым :id-маршрутам (baseline 66→0)
check-route-auth теперь распознаёт router-level guards (router.use(<guard>)) — ушли ложные срабатывания (admin/permissions/flashcards/lessons/… защищены на уровне роутера, что линтер уже принимает как authMiddleware). Из 66 осталось 8 действительно безавторизационных :id-маршрутов — все публичные по дизайну (гостевая доска по секретному токену, справочные данные Red Book, список тем предмета): помечены @public-by-design после проверки (мутации требуют auth). Baseline опущен до 0 — новые незащищённые маршруты теперь сразу падают в хуке. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,17 +51,29 @@ const GUARDS = [
|
||||
];
|
||||
|
||||
// Baseline: number of unprotected :id-routes.
|
||||
// Reconciled 2026-06-11: drifted 56→66 via branch merges (lab-content-engine,
|
||||
// red-book, exam-prep и др.) — pre-commit hook не запускается на merge, поэтому
|
||||
// маршруты пришли без проверки. Это уже смерженный долг, а не новый риск.
|
||||
// ONLY decrease this over time — never increase it (кроме сверки с уже смерженным).
|
||||
const BASELINE = 66;
|
||||
// 2026-06-11: линтер научился видеть router-level guards (router.use(<guard>)),
|
||||
// что убрало ложные срабатывания (admin/permissions/flashcards/… защищены на
|
||||
// уровне роутера). Оставшиеся 8 публичных маршрутов (guest-доска по токену,
|
||||
// справочные данные red-book, список тем) помечены @public-by-design. Долг закрыт.
|
||||
// ONLY decrease this over time — never increase it.
|
||||
const BASELINE = 0;
|
||||
|
||||
function scanFile(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
const issues = [];
|
||||
|
||||
// Router-level guard: `router.use(<guard>)` without a leading path string
|
||||
// protects every route declared after it (same guards accepted inline).
|
||||
// Find the earliest such line so those routes aren't false-flagged.
|
||||
let globalGuardLine = Infinity;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const t = lines[i].trim();
|
||||
if (!t.startsWith('router.use(')) continue;
|
||||
if (/^router\.use\(\s*['"`]/.test(t)) continue; // path-scoped — not global
|
||||
if (GUARDS.some(g => t.includes(g))) { globalGuardLine = i; break; }
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
@@ -74,6 +86,9 @@ function scanFile(filePath) {
|
||||
if (!pathMatch) continue;
|
||||
if (!pathMatch[1].includes(':')) continue;
|
||||
|
||||
// Protected by a router-level guard declared earlier in this file
|
||||
if (i > globalGuardLine) continue;
|
||||
|
||||
// Collect the full route call (may span multiple lines)
|
||||
let callText = line;
|
||||
let j = i + 1;
|
||||
|
||||
Reference in New Issue
Block a user