Skip to content

fix(api-map): detect router.use() middleware to fix auth_protected false-negative (closes #214) - #226

Closed
Wolfvin wants to merge 1 commit into
mainfrom
fix/issue-214-router-use-middleware
Closed

fix(api-map): detect router.use() middleware to fix auth_protected false-negative (closes #214)#226
Wolfvin wants to merge 1 commit into
mainfrom
fix/issue-214-router-use-middleware

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Closes #214

Patch Peta (delta struktural)

Files:

  • tests/test_apimap_router_middleware.py — 32 unit tests reproducing router.use(mw) pattern (issue fix(api-map): router.use() middleware not detected, causes false-negative auth_protected #214 DoD feat: integrate codelens-watch.py into cmd_watch — debounce, clean output, outline/summary #4) + backward-compat checks for app.use() global middleware (DoD refactor: backend-focused cleanup — remove hardcoded paths, garbage files, frontend deps #3) + router-var tagging tests + auth-pattern expansion tests.
    M scripts/apimap_engine.py — added _detect_router_vars() helper (shared between routes & middleware extraction); extended Router() regex to accept express.Router() / koa.Router(); _extract_js_routes now records router_var on every JS route dict; _extract_js_middleware emits new scope="router:" for .use(mw); post-processing attaches router-scoped mw only to routes on the same router_var (no leak); AUTH_MIDDLEWARE_PATTERNS extended with requirepermission/haspermission/checkpermission/verifypermission/ensurepermission.
    Endpoints: none
    Schema: each JS route dict now carries a new router_var field (string, the receiver of .get/.post/.put/.delete/.patch/.head/.options). Middleware dicts now emit scope values router:<var> and router-path:<var>:<path> in addition to the pre-existing global and path:<path>.
    Konvensi: when adding new JS framework support, pass router_vars into _extract_js_routes / _extract_js_middleware rather than re-detecting inline. When adding new auth-middleware name patterns, append to AUTH_MIDDLEWARE_PATTERNS in lowercase (matching is substring + case-insensitive).

Klaim + Bukti

import: python3 -c "import sys; sys.path.insert(0,'scripts'); import apimap_engine, commands.api_map" → exit 0 (no errors)
unit tests: python3 -m pytest tests/test_apimap_router_middleware.py tests/test_command_count.py tests/test_command_registry.py tests/test_framework_detect.py tests/test_design_doc_check.py → 113 passed in 2.44s
dod #1 (auth_protected count >> 3): end-to-end on KDS-style fixture (4 auth-bearing routes + 2 public routes) → stats.auth_protected: 4 (was 0 before fix; baseline in issue body says 3 for the real KDS backend). Sample output:
total_routes=6 auth_protected=4 public=2 GET /api/accounting/invoices auth=True mw=['authMiddleware','requireOutletAccess','requirePermission'] POST /api/accounting/invoices auth=True mw=['authMiddleware','requireOutletAccess','requirePermission'] DELETE /api/accounting/invoices/:id auth=True mw=['authMiddleware','requireOutletAccess','requirePermission'] PUT /api/accounting/invoices/:id auth=True mw=['authMiddleware','requireOutletAccess','requirePermission'] GET /health auth=False mw=[] GET /version auth=False mw=[]
dod #2 (no over-detection): publicRouter routes /health and /versionauth_protected: False, mw_chain=[] — accountingRouter.use(authMiddleware) does NOT leak.
dod #3 (no regression app.use global): app.use(cors()); app.use(jwt); app.get('/users', ...); app.get('/health', ...) → every route has mw_chain containing cors and jwt, all routes auth_protected=True (jwt is auth). Same as pre-fix behavior.
dod #4 (new unit test): tests/test_apimap_router_middleware.py::TestMapApiRoutesIssue214::test_auth_protected_count_above_baseline and 31 other tests → 32 passed.
classify: _classify_middleware('requirePermission') → 'auth' (was 'custom' before fix); same for hasPermission, checkPermission, verifyPermission, ensurePermission.

Klaim yang TIDAK bisa dibuktikan di sini:

  • api-map <real-kds-workspace> count comparison before/after: tidak dijalankan karena KDS backend workspace tidak tersedia di sesi worker ini. Issue body melaporkan auth_protected: 3 dari total_routes: 160 di KDS asli; verifikasi pada codebase nyata diserahkan ke BOS saat review. Reproduksi di fixture sintetik (4 routes auth, 2 public) sudah memvalidasi structural fix.

Catatan Pendekatan

Issue body menyebutkan _extract_js_middleware baris ~726 dan assembly middleware_chain per route baris ~340-370 sebagai starting point. Investigasi menemukan bahwa:

  • Root cause memang di _extract_js_middleware (regex receiver hardcoded ke app|server|fastify|hono), tetapi
  • Fix juga perlu menyentuh _extract_js_routes (untuk record router_var field pada setiap route) dan post-processing block di map_api_routes (untuk attach router-scoped middleware hanya ke route dengan router_var yang cocok).
  • Tambahan: regex deteksi Router() assignments di-expand untuk accept express.Router() / koa.Router() (pre-fix hanya match bare Router()). Constraint di issue body eksplisit menyebutkan = Router() atau = express.Router() — keduanya sekarang dikenali.

Tidak ada pendekatan alternatif yang dipilih — implementasi mengikuti instruksi issue.

Breaking / Found-not-fixed

  • Pre-existing gap (out of scope): middleware dengan scope="path:<path>" (dari app.use('/path', mw)) maupun scope="router-path:<var>:<path>" (dari <routerVar>.use('/path', mw)) saat ini dikumpulkan ke middleware_map tetapi TIDAK di-attach otomatis ke middleware_chain per route di post-processing. Ini adalah pre-existing behavior — path: scope sudah ada sebelum PR ini dan tidak di-attach; saya menambahkan router-path: scope dengan behavior yang sama (konsisten). Kalau BOS mau path-scoped middleware ikut di-attach ke route yang match path-nya, itu follow-up issue terpisah. Tidak dibuat issue otomatis karena ini bukan regression dari PR ini — hanya dokumen konstatasi bahwa layer itu belum diimplementasikan.
  • AUTH_MIDDLEWARE_PATTERNS expansion: requireOutletAccess (disebut di issue body) TIDAK diklasifikasi sebagai auth karena substring access terlalu broad (bisa false-positive ke banyak nama variabel unrelated). Auth-protected count di fixture tetap naik ke 4 karena authMiddleware (substring auth) sudah cukup. Kalau BOS ingin requireOutletAccess dianggap auth, perlu pattern yang lebih spesifik.
  • Pre-existing test failures (bukan dari PR ini): tests/test_cli.py::TestArgparseFormatConflictRegression::test_scan_with_format_long_does_not_crash dan test_scan_with_format_short_does_not_crash fail karena subprocess timeout 60s (codelens scan . lambat tanpa pre-built registry di env ini). tests/test_compact_format.py::TestGraphSchemaCommand::test_returns_correct_counts fail karena tree-sitter-typescript grammar tidak terinstall di env worker (CALLS edge count beda: 97 vs expected 76). Keduanya ter-verifikasi gagal juga pada clean main checkout — bukan regression dari PR ini.

…lse-negative (closes #214)

_extract_js_middleware() hardcoded its regex receiver to app|server|fastify|hono,
so any <routerVar>.use(middleware) call on a custom Router() instance (the standard
Express modular-routing pattern) was silently dropped. Every route registered via
that router was then reported as auth_protected:false even when the router
explicitly mounted authMiddleware / requirePermission(...) etc.

Fix:
- _detect_router_vars() helper extracted and shared between _extract_js_routes
  (prefix + router_var tagging) and _extract_js_middleware (router-scoped .use()).
  Regex now also accepts express.Router() / koa.Router() (previously only bare
  Router()).
- _extract_js_middleware emits a new 'router:<var>' scope for <routerVar>.use(mw)
  calls where <routerVar> is a known Router() instance. Unknown receivers are
  skipped (no false positives from arbitrary foo.use(bar)).
- Post-processing attaches router-scoped middleware ONLY to routes whose
  router_var matches — no leak to routes on other routers or to top-level
  app.get/post routes.
- Each JS route dict now carries a 'router_var' field (the receiver of .get/.post
  /etc.) so router-scoped middleware can be attached precisely.
- AUTH_MIDDLEWARE_PATTERNS extended with requirepermission/haspermission/
  checkpermission/verifypermission/ensurepermission (JS camelCase permission
  middleware) so requirePermission('admin') is classified as 'auth'.

Backward compat: app.use(mw) / server.use(mw) / fastify.use(mw) / hono.use(mw)
still emit scope='global' and attach to every route as before. Signatures of
_extract_js_routes and _extract_js_middleware accept router_vars:Optional[...]=None
(inline detection fallback for legacy direct callers).
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sonarqubecloud

Copy link
Copy Markdown

@Wolfvin

Wolfvin commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

PR ditutup — collision dengan PR #224 yang sudah merge duluan (issue #214 sudah closed).

Kedua implementasi kualitasnya bagus (24 vs 32 test, keduanya well-documented). Tapi ditemukan gap correctness di implementasi ini yang tidak ada di #224: post-processing assembly (elif scope.startswith("router:")) di PR ini HANYA cek route.get("router_var") == router_var — TIDAK cek file match.

Ini berpotensi bug nyata: kalau 2 file BERBEDA punya router variable dengan NAMA SAMA (contoh sangat umum: banyak route file konvensinya const router = Router() — nama generik, bukan accountingRouter/assignmentsRouter), middleware dari router di file A akan salah ter-attach ke routes di file B yang kebetulan punya variable bernama sama.

PR #224 eksplisit cek KEDUANYA: route.get("file") == mw_file and route.get("router_var") == router_var (plus ada test test_no_cross_leak_between_files yang cover skenario ini). Test suite di PR ini (32 test) tidak punya skenario 2-file-nama-var-sama untuk menangkap gap ini.

Terima kasih untuk investigasi mendalam dan tambahan pattern auth (haspermission/checkpermission/verifypermission/ensurepermission) — kalau mau, silakan buka PR baru khusus untuk expand AUTH_MIDDLEWARE_PATTERNS itu saja (di atas main yang sudah include #224), karena itu genuinely value tambahan yang tidak overlap.

@Wolfvin Wolfvin closed this Jul 12, 2026
@Wolfvin
Wolfvin deleted the fix/issue-214-router-use-middleware branch July 12, 2026 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(api-map): router.use() middleware not detected, causes false-negative auth_protected

1 participant