# Code Review TODO — Clearview **Aangemaakt:** 2026-05-19 **Branch bij review:** `refactor/scanner-package-frontend` **Scope:** Eerste volledige review (~7.100 regels code) **Totaal:** 13 CRITICAL · 19 HIGH · 14 MEDIUM · 1 LOW Werkvolgorde: alle CRITICAL eerst (P0), daarna HIGH (P1), dan MEDIUM/LOW (P2). Per item staan severity, bestand(en):regel(s), en de gewenste fix. --- ## P0 — CRITICAL (eerst dichten) ### Auth & secrets - [ ] **Geen authenticatie op enig API-endpoint** - `containers/clearview/src/clearview_app/main.py` (alle `/api/` routes) - Fix: API-key via `X-API-Key` header met FastAPI `Security()` dependency, of Bearer-token op alle `/api/` routes. - [ ] **Client secrets staan plaintext in DB** - `containers/clearview/src/clearview_app/models.py:21` (`TenantProfile.client_secret`) - `containers/clearview/src/clearview_app/models.py:45` (`ScanJob.auth_client_secret`) - Fix: encrypt-at-rest met `cryptography.fernet`; key via env var. Decrypt enkel in geheugen bij gebruik. - [ ] **`.env` niet in `.gitignore`** - `.gitignore`, `stack/.env` - Fix: voeg `stack/.env` en `**/.env` toe aan `.gitignore`; lever `stack/.env.example` met placeholders; verifieer dat `.env` nog niet in git history zit (anders rotate credentials). - [ ] **Hardcoded DB-fallback `clearview:clearview`** - `containers/clearview/src/clearview_app/config.py:17-19` - Fix: verwijder default; `raise RuntimeError("DATABASE_URL required")` als env ontbreekt. - [ ] **Adminer publiek op `0.0.0.0:8081`** - `stack/docker-compose.yml:44-46` - Fix: bind aan `127.0.0.1:${ADMINER_PORT:-8081}:8080` of verwijder uit prod-compose. ### Injectie & exfiltratie - [ ] **XSS via ongescapete velden in `innerHTML`** (3 vindplaatsen) - `containers/clearview/site/app.js:658-676` (`job.id`, `job.source_type`, `job.items_scanned`) - `containers/clearview/site/app.js:885-894` (`job.status`, `total/processed/successful/failed_targets`, `items_scanned`) - `containers/clearview/site/app.js:175-184` (`statusBadge()` zonder escape op `status`) - Fix: consequent `escHtml()` op alle API-velden, óók ID's en numerieke. Geen uitzonderingen. - [ ] **Open redirect via `payload.connect_url`** - `containers/clearview/site/app.js:472` - Fix: valideer `new URL(payload.connect_url).protocol === 'https:'` + host-allowlist (`login.microsoftonline.com`). - [ ] **SSRF / token-exfiltratie via `@odata.nextLink`** - `containers/clearview/src/clearview_app/scanners/sharepoint.py:547-553` - `containers/clearview/src/clearview_app/scanners/entra.py:227-272` - Fix: vergelijk `urlparse(next_url).netloc == urlparse(original_url).netloc`; gooi anders een `RuntimeError`. - [ ] **Header injection in `Content-Disposition`** - `containers/clearview/src/clearview_app/main.py:701-705` - Fix: type de route-parameter als `uuid.UUID` zodat FastAPI `job_id` valideert; `urllib.parse.quote(filename, safe="")`. --- ## P1 — HIGH ### Correctheid - [ ] **Token cache zonder TTL — workers crashen na 1 uur** - `containers/clearview/src/clearview_app/scanners/sharepoint.py:34, 512-543` - Fix: bewaar `expires_at = time.time() + result["expires_in"] - 60`; invalideer in `_get_token_for_host`. - [ ] **MSAL `ConfidentialClientApplication` per aanroep** - `containers/clearview/src/clearview_app/scanners/sharepoint.py:530` - Fix: module-level dict `(tenant_id, client_id, auth_method) -> app`. Hergebruik object. - [ ] **`_TOKEN_CACHE` zonder lock (race in multi-thread)** - `containers/clearview/src/clearview_app/scanners/sharepoint.py:34` - Fix: `threading.Lock` rond check-then-write, of `functools.lru_cache` + TTL-wrapper. - [ ] **Race condition in worker: niet-atomaire job-claim** - `containers/clearview/src/clearview_app/worker.py:48-68` - Fix: één `UPDATE scan_jobs SET status='running' WHERE id=:id AND status='queued' RETURNING id` met `FOR UPDATE SKIP LOCKED`. - [ ] **Auto-refresh race in frontend** - `containers/clearview/site/app.js:1009-1013` + alle `tick()` callsites - Fix: `AbortController` per render; vorige request cancelen voordat nieuwe gestart wordt. - [ ] **Event-listener accumulatie / re-render-pattern** - `containers/clearview/site/app.js:238-270, 678-702` - Fix: event delegation op stabiele container (`els.jobsTableBody.addEventListener('click', ...)`). - [ ] **Sequentieel awaiten van onafhankelijke calls** - `containers/clearview/site/app.js:554-555, 999, 1281, 1364` - Fix: `await Promise.all([refreshJobs(), refreshSelectedJob()])`. - [ ] **Niet-afgehandelde floating promise** - `containers/clearview/site/app.js:1143` - Fix: `.catch(err => showFeedback(...))` op `testTargetConnection(...)`. - [ ] **OAuth state-store is in-memory dict (breekt bij `--workers >1`)** - `containers/clearview/src/clearview_app/onboarding.py:31, 134-145` - Fix: state opslaan in DB (tabel `oauth_states` met `created_at`, `consumed_at`) of Redis. - [ ] **`scan_type` ongevalideerd** - `containers/clearview/src/clearview_app/schemas.py:36`, `main.py:178, 207` - Fix: `Literal["sharepoint","sharepoint_root","mailbox","entra_groups"]` in Pydantic schema. - [ ] **`datetime.utcnow()` deprecated, timezone-naive** (overal) - `main.py`, `worker.py`, `models.py:26-27`, `cert.py:33` - Fix: `datetime.now(timezone.utc)`; `DateTime(timezone=True)` in SQLAlchemy-kolommen. - [ ] **`ThreadPoolExecutor(max_workers=1)` per target** - `containers/clearview/src/clearview_app/worker.py:307` - Fix: gedeelde executor; documenteer dat `future.cancel()` lopende scan niet onderbreekt. - [ ] **Geen throttling-respect bij 429 in item-loop** - `containers/clearview/src/clearview_app/scanners/sharepoint.py:603-619` - Fix: batching via `$expand=RoleAssignments` of exponential backoff op item-niveau. ### Hardening - [ ] **Container draait als root** - `containers/clearview/Dockerfile` - Fix: `RUN adduser --system --ingroup clearview clearview` + `USER clearview`. - [ ] **`.deb` van packages.microsoft.com zonder checksum** - `containers/clearview/Dockerfile:17-19` - Fix: hardcoded SHA256 + `sha256sum --check`, of officiële GPG-key via `signed-by`. - [ ] **`Install-Module ExchangeOnlineManagement` zonder versie-pin** - `containers/clearview/Dockerfile:24-26` - Fix: `-RequiredVersion 3.7.0` (of huidige geteste versie). - [ ] **Graph-foutberichten gelekt richting frontend** - `containers/clearview/src/clearview_app/onboarding.py:170, 188` - Fix: volledig log naar server-side DEBUG; aan client alleen generieke code + HTTP-status. - [ ] **OData-filter injection via displayName/mail** - `containers/clearview/src/clearview_app/scanners/entra.py:178-196` - Fix: `urllib.parse.quote(cleaned.replace("'", "''"), safe="")`. - [ ] **PowerShell-args zonder UPN-validatie** - `containers/clearview/src/clearview_app/scanners/mailbox.py:181-190` - `containers/clearview/src/clearview_app/scanners/exo_scripts/get-permissions.ps1:107` - Fix: `re.fullmatch(r"[^@\s]{1,64}@[^@\s]{1,255}", upn)` vóór `_run_pwsh`. ### Architectuur - [ ] **`main.py` is 1139 regels — splitsen** - Fix: `routers/{tenants,jobs,onboarding}.py`, `services/job_service.py`, `export.py`. - [ ] **`sharepoint.py` is 722 regels — splitsen** - Fix: `_auth.py`, `_http.py`, `sharepoint.py` (publieke scanfuncties), `sharing_links.py`. - [ ] **`_ensure_schema_columns()` met 18 raw `ALTER TABLE`** - `containers/clearview/src/clearview_app/main.py:1115-1139` - Fix: vervang door Alembic; commit baseline-migratie + history. --- ## P2 — MEDIUM / LOW - [ ] **`app.js` 1586 regels, geen build step** — splits in ES-modules + esbuild/rollup; verwijder IIFE-wrapper. - [ ] **Focus management bij route-wissel ontbreekt** — `app.js:1529-1551` — focus naar `