Security: can_access_list() grants any authoritative user access to ALL private lists (missing per-list authorization)
- Dominant language
- Python
- Stars
- 45
- Forks
- 23
- PR merge metrics
- No merged PRs in 30d
Description
server/plugins/aaa.py:can_access_list() ignores the requested list id and grants access to every private list to any session whose OAuth login is authoritative. There is no per-list membership / subscriber / moderator / owner check anywhere in the backend, so a user who is authorized for one private list (or for none) can read the archives of all private lists on the instance.
This was reported privately to security@apache.org first, the PMC confirmed it is a fix-before-release item (no CVE, as Foal is unreleased) and asked me to raise it here for public discussion / a PR. The analysis is entirely source-based against master.
Affected: apache/ponymail-foal master (git-deployed; unreleased). Verified at 88f227b1d2.
Root cause:
```python
# server/plugins/aaa.py
def can_access_email(session, email) -> bool:
if not email.get("private", True): # public -> always
return True
return can_access_list(session, email.get("list_raw", None))
def can_access_list(session, _listid) -> bool:
# If logged in via a known oauth, we assume access for now...TO BE CHANGED
if session.credentials and session.credentials.authoritative:
return True
return False
```
_listid is never used. The developer comment (...TO BE CHANGED) already flags this as a placeholder. authoritative is set for anyone whose OAuth provider is listed in oauth.authoritative_domains (server/plugins/session.py):
creds["authoritative"] = internal.get("oauth_provider") in server.config.oauth.authoritative_domains
Reachability:
server/plugins/messages.py:get_email() gates returned documents on can_access_email() -> can_access_list(), and /api/email, /api/mbox, /api/thread, /api/stats all reach it. An authoritative-domain user requesting a private list's messages receives them.
Impact:
Cross-list disclosure of private archives. Severity scales with oauth.authoritative_domains:
- default is [] (nobody has private access — not exposed out of the box);
- a single-org OAuth provider -> any org member can read all private lists (security@, board@, PMC-private, …);
- a broad public provider (e.g. a generic Google/GitHub domain) -> effectively any account holder can read all private archives.
There is currently no configuration that limits a user to their own private lists. Access is all-or-nothing per authoritative domain.
Thanks!
Contributor guide
No contributing guide indexed for this repository
Research direction
Read server/plugins/aaa.py first, then trace authoritative credential setup in server/plugins/session.py and document retrieval through server/plugins/messages.py. Follow the /api/email, /api/mbox, /api/thread, and /api/stats paths to understand the affected checks. Done means private-list access is verified per requested list rather than granted to every authoritative session.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- authorization, backend, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100