# Adversarial Review 002 - Post Spec 004 (Projects & Usage) **Date**: 2026-03-07 **Scope**: Full codebase review after specs 001-004 **Tests**: 53 total (17 core + 36 server), clippy clean **Verified**: Against real forest-server on localhost:4040 --- ## 1. Architecture: Repeated gRPC Calls Per Page Load **Severity: High** Every authenticated platform page (`projects_list`, `project_detail`, `usage`) calls `list_my_organisations` to verify membership. This means: - `/orgs/testorg/projects` -> 1 call to list orgs + 1 call to list projects = **2 gRPC calls** - `/orgs/testorg/projects/my-api` -> 1 call to list orgs + 1 call to list artifacts = **2 gRPC calls** - Dashboard -> 1 call to list orgs (redirect) then the target page makes its own calls This is the same pattern we fixed for `get_user()` in spec 003 (caching user in session). The org list should be cached in the session too, or at minimum passed through from the `Session` extractor. **Recommendation**: Cache the user's org memberships in `SessionData` / `CachedUser`. Refresh on session refresh or after a configurable TTL. This eliminates the most expensive repeated call. --- ## 2. Architecture: Two Traits, One Struct, Inconsistent Error Handling **Severity: Medium** `GrpcForestClient` now implements both `ForestAuth` and `ForestPlatform`. The `authed_request` helper is duplicated: - `GrpcForestClient::authed_request()` returns `AuthError` - `platform_authed_request()` is a free function returning `PlatformError` Same logic, two copies, two error types. `AppState` holds `Arc` + `Arc` which in production point to the same struct. This is fine for testability but means the constructors are getting wide (4 args now). **Recommendation**: Consider a single `ForestClient` trait that combines both, or unify the auth helper into a generic form. Not urgent but will become pain as more services are added. --- ## 3. Security: Org Name in URL Path is User-Controlled **Severity: Medium** Routes use `{org}` from the URL path and pass it directly to gRPC calls and template rendering: - `format!("{org} - Projects - Forage")` in HTML title - `format!("Projects in {org}")` in meta description MiniJinja auto-escapes by default in HTML context, so XSS via `