From 22e77e02f0ac6270d892191140aa3af18bff5cad Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 7 Jun 2026 20:39:04 +0200 Subject: [PATCH] fix(manager-core): F-S-003 require authenticated principal for users::find_by_email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit find_by_email gated on AppUsersRead via `require`, which short-circuits when cx.principal == None. An anonymous public-HTTP script could iterate emails to enumerate registered users, then pivot to login or request_password_reset. Tighten: explicitly return Forbidden when cx.principal is None — scripts that need a "does this email exist" probe from a public route must wrap the call in their own auth gate. AUDIT.md anchor: F-S-003. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/manager-core/src/users_service.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/manager-core/src/users_service.rs b/crates/manager-core/src/users_service.rs index 4f936c4..4dd6fd9 100644 --- a/crates/manager-core/src/users_service.rs +++ b/crates/manager-core/src/users_service.rs @@ -496,6 +496,14 @@ impl UsersService for UsersServiceImpl { cx: &SdkCallCx, email: &str, ) -> Result, UsersError> { + // F-S-003: an anonymous public-HTTP script could iterate emails + // via find_by_email to enumerate registered users (the `require` + // helper short-circuits on principal == None). Tighten: require + // an authenticated principal — scripts that need a "does this + // email exist?" probe must wrap it in their own auth gate. + if cx.principal.is_none() { + return Err(UsersError::Forbidden); + } self.require(cx.principal.as_ref(), Capability::AppUsersRead(cx.app_id)) .await?; let normalized = validate_email(email)?;