Finding
crates/paroche/src/lib.rs:98 applies CorsLayer::permissive() to the router as a whole. That same
router nests the authentication routes at crates/paroche/src/lib.rs:33:
.nest("/api/auth", routes::user::auth_routes())
...
.layer(CorsLayer::permissive())
CorsLayer::permissive() allows any origin, any method, and any header. Applied at the router level it
covers every nested route, so the authentication surface is included rather than exempted.
Evidence
crates/paroche/src/lib.rs:98 — .layer(CorsLayer::permissive()), router-level.
crates/paroche/src/lib.rs:33 — .nest("/api/auth", routes::user::auth_routes()) on the same router.
- Auth endpoints confirmed live by the integration tests:
crates/archon/tests/config_reload_e2e.rs:145
posts to /api/auth/login, :308 posts to /api/auth/refresh.
Why this matters
Permissive CORS on an authentication surface means any origin can issue cross-origin requests to login
and token-refresh endpoints and read the responses. The severity depends on how the resulting session
is carried: if credentials ride in a cookie, permissive() is the precondition for cross-site request
forgery against those endpoints; if a bearer token is returned in the body, any origin the user visits
can read a token minted for this service.
The exposure is not theoretical for this repo specifically. harmonia is a media server — the class of
software that is routinely reachable from a browser on the same network, and often deliberately exposed
beyond it. That is materially different from a loopback-bound service, where the same code would be
low-risk.
permissive() is the correct default while a frontend is being developed against a moving API, which
is very likely how it got here. It is the wrong default once auth routes are on the same router.
Desired correction
Do not apply a blanket permissive layer over a router carrying authentication. Either:
- Replace
permissive() with an explicit origin allow-list, method set, and header set, driven by
configuration rather than hardcoded, so a deployment declares who may call it; or
- Scope CORS to the routes that genuinely need cross-origin access and leave
/api/auth off it.
If credentials are cookie-borne, note that CorsLayer::permissive() and allow_credentials(true) are
mutually exclusive in tower_http by design — the wildcard origin is rejected with credentials — so
confirm which mode the client actually relies on before changing it, or the fix will surface as a
client breakage rather than as the security change it is.
Done when: a cross-origin request from an unlisted origin to /api/auth/login is rejected by the
CORS layer, and a test pins that rejection so the permissive default cannot return unnoticed.
Found during a fleet-wide security sweep scoped to kanon; this instance is in harmonia and is the
highest-severity item the sweep produced, because unlike the kanon surfaces it sits on a service
designed to be reachable.
Finding
crates/paroche/src/lib.rs:98appliesCorsLayer::permissive()to the router as a whole. That samerouter nests the authentication routes at
crates/paroche/src/lib.rs:33:CorsLayer::permissive()allows any origin, any method, and any header. Applied at the router level itcovers every nested route, so the authentication surface is included rather than exempted.
Evidence
crates/paroche/src/lib.rs:98—.layer(CorsLayer::permissive()), router-level.crates/paroche/src/lib.rs:33—.nest("/api/auth", routes::user::auth_routes())on the same router.crates/archon/tests/config_reload_e2e.rs:145posts to
/api/auth/login,:308posts to/api/auth/refresh.Why this matters
Permissive CORS on an authentication surface means any origin can issue cross-origin requests to login
and token-refresh endpoints and read the responses. The severity depends on how the resulting session
is carried: if credentials ride in a cookie,
permissive()is the precondition for cross-site requestforgery against those endpoints; if a bearer token is returned in the body, any origin the user visits
can read a token minted for this service.
The exposure is not theoretical for this repo specifically. harmonia is a media server — the class of
software that is routinely reachable from a browser on the same network, and often deliberately exposed
beyond it. That is materially different from a loopback-bound service, where the same code would be
low-risk.
permissive()is the correct default while a frontend is being developed against a moving API, whichis very likely how it got here. It is the wrong default once auth routes are on the same router.
Desired correction
Do not apply a blanket permissive layer over a router carrying authentication. Either:
permissive()with an explicit origin allow-list, method set, and header set, driven byconfiguration rather than hardcoded, so a deployment declares who may call it; or
/api/authoff it.If credentials are cookie-borne, note that
CorsLayer::permissive()andallow_credentials(true)aremutually exclusive in
tower_httpby design — the wildcard origin is rejected with credentials — soconfirm which mode the client actually relies on before changing it, or the fix will surface as a
client breakage rather than as the security change it is.
Done when:a cross-origin request from an unlisted origin to/api/auth/loginis rejected by theCORS layer, and a test pins that rejection so the permissive default cannot return unnoticed.
Found during a fleet-wide security sweep scoped to kanon; this instance is in harmonia and is the
highest-severity item the sweep produced, because unlike the kanon surfaces it sits on a service
designed to be reachable.