feat/atlas-scan
needs attentionviewing older commitf5d3266 · incrementalpre-PRreviewed 2026-08-03 19:40 UTC0H · 3M · 8L · 3I- Purpose
- Build and gate the private Batu Atlas — a siting intelligence suite with sensitive energy-infrastructure layers (Power Finder, gas pipelines, acuiferos, congestion zones, cortocircuito) served only to authenticated platform users.
- Goal
- Move atlas-private data from web/public (publicly accessible) to platform (auth-gated) and implement a secure, two-layer authenticated file-serving route handler.
- Sub-goals
- SG-1: Migrate atlas-private data bundle from apps/web/public/atlas to apps/platform/atlas-private (outside public/)
- SG-2: Implement authenticated route handler at /atlas with fail-closed 401 on invalid session
- SG-3: Fix Next.js limitation: optional catch-all [[...path]] not supported in route handlers — split into /atlas and /atlas/[...path]
- SG-4: Add <base href=/atlas/> injection for relative fetch resolution
- SG-5: Gate /atlas in proxy middleware (redirect-to-login) + handler (JWT re-validate)
- SG-6: Write congestion analysis pipeline scripts (scripts/atlas/congestion/)
- SG-7: Write pland (PowerFinder land) data pipeline scripts (scripts/atlas/pland/)
- What
- Route handler split from single [[...path]] (broken) to two explicit handlers (/atlas root + /atlas/[...path]) sharing serveAtlas() helper. Updated next.config.ts outputFileTracingIncludes to cover both route keys. Updated test file to import from the two new routes.
- Why
- Next.js route handlers do not support optional catch-all [[...path]] — the build succeeds but the router never matches the route, causing /atlas to fall through to the [locale] tree and bounce in a redirect loop.
- Areas
- apps/platform+241−37apps/web+19−21scripts/atlas+6000−0
- Blast
- ~6300 lines added across 100+ files. Platform: 5 code files (route handlers, serve lib, test, next.config). Scripts: 60+ Python/data pipeline files (analysis-only, no production impact). Web: atlas page updated.
Findings · 14
security4
CDN script (maplibre-gl) loaded without Subresource Integrity
apps/platform/atlas-private/index.html
maplibre-gl@4.7.1 from unpkg.com with no integrity= attribute. CDN compromise → script runs in authenticated session, exfiltrates private GeoJSON layers. Fix: add integrity="sha384-..." or vendor the library.
Middleware matcher skips proxy for .svg and .png paths under /atlas
apps/platform/src/lib/supabase/proxy.ts
Handler (layer 2) still returns 401 — no data leak. But redirect-to-login UX absent for those extensions.
Missing X-Content-Type-Options: nosniff header
apps/platform/src/app/atlas/_lib/serve.ts:77
One-liner fix: add 'X-Content-Type-Options': 'nosniff' to the headers object.
SVG in MIME allowlist can execute JS when opened directly in browser tab
apps/platform/src/app/atlas/_lib/serve.ts:30
No SVG files currently in atlas-private/ and content is source-controlled. Theoretical risk if a malicious SVG is committed.
conventions2
String.replace('<head>') only patches first occurrence — fragile on casing drift
apps/platform/src/app/atlas/_lib/serve.ts:73
Safe for current artifact. Consider /<head>/i for forward resilience.
TYPES allowlist omits .js and .css — future bundled atlas rebuilds would 404
apps/platform/src/app/atlas/_lib/serve.ts:26
Current artifact uses CDN-only JS/CSS. If rebuilt as Vite/webpack bundle, those requests will 404 silently.
tests5
401 fail-closed test only exercises getRoot() — getPath() auth path untested
apps/platform/src/lib/supabase/__tests__/atlas-route-gate.test.ts:75
Add: call(['data','pf.json']) with sessionOk=false should return 401. The two routes are separate imports — a wiring accident removing the session check from [...path]/route.ts would go undetected.
Cache-Control and X-Robots-Tag not verified on data file responses
apps/platform/src/lib/supabase/__tests__/atlas-route-gate.test.ts:101
Primary defence against CDN caching of authenticated content. Set in serveAtlas() unconditionally but only verified on the index.html test case.
No test for non-existent file within an allowed extension (catch branch)
apps/platform/src/lib/supabase/__tests__/atlas-route-gate.test.ts:101
Add: call(['data','missing.json']) with sessionOk=true should return 404.
Proxy gate tests don't cover /atlas/ subpaths
apps/platform/src/lib/supabase/__tests__/atlas-route-gate.test.ts:34
Both proxy tests use req('/atlas'). A regression in startsWith('/atlas/') branch would go undetected.
No positive test for .geojson or .csv extension types
apps/platform/src/lib/supabase/__tests__/atlas-route-gate.test.ts:101
Private atlas key layers (gas_pipelines, acuiferos, parques_industriales) are .geojson. Removing .geojson from TYPES would not be caught.
improvement3
path.relative(BASE, abs).startsWith('..') is more idiomatic traversal guard
apps/platform/src/app/atlas/_lib/serve.ts:57
Canonical Node static-file server pattern; makes the invariant self-evident vs BASE + path.sep.
Buffer round-trip in HTML injection is unnecessary
apps/platform/src/app/atlas/_lib/serve.ts:66
NextResponse accepts a string body directly — the intermediate Buffer.from is dead work.
Unused NextRequest import in [...path]/route.ts
apps/platform/src/app/atlas/[...path]/route.ts:2
_req is never accessed; import can be dropped or replaced with the global Request type.
History · 16 commits
- 91aaedfsafeincremental0H · 1M · 1L2026-08-12 17:35
- cb8d915needs attentionincremental2H · 5M · 3L2026-08-12 17:29
- 4cbbe8aneeds attentionincremental0H · 3M · 8L2026-08-12 14:01
- 4aa3c02needs attentionincremental2H · 3M · 3L2026-08-12 02:35
- f5630b2needs attentionincremental0H · 4M · 5L2026-08-12 02:17
- 28fde5bneeds attentionincremental1H · 2M · 2L2026-08-12 01:53
- 0babe51needs attentionincremental1H · 2M · 5L2026-08-12 01:23
- 50e8a8cneeds attentionincremental3H · 5M · 7L2026-08-11 23:58
- 3af4686needs attentionincremental3H · 5M · 5L2026-08-03 20:07
- f5d3266needs attentionincremental0H · 3M · 8L2026-08-03 19:40current
- 9f8b61aneeds attentionincremental0H · 2M · 3L2026-08-03 19:15
- ea51fa0needs attentionincremental1H · 4M · 4L2026-08-03 19:02
- 2b33f2fneeds attentionincremental0H · 1M · 1L2026-07-18 05:09
- 2f8cf79needs attentionincremental1H · 3M · 4L2026-07-18 00:59
- 1616332safeincremental0H · 0M · 1L2026-07-18 00:22
- e997fd8needs attentionfull1H · 4M · 6L2026-07-17 23:53