Nothing limits how fast an account can make the server fetch a URL, or hit the API #112

Closed
opened 2026-09-07 18:54:29 +00:00 by tiagoagueda · 0 comments
Owner

Found by

A security audit. Not reachable by a stranger -- every surface here needs an account or a
token -- but nothing bounds what an account may do once it has one.

What is already protected

allauth's own limits are in force with a cache behind them, so authentication is covered:

login          30/m/ip        login_failed   10/m/ip, 5/300s/key
signup         20/m/ip        reset_password 20/m/ip, 5/m/key

TrustedProxyMiddleware also gets the key right: it takes the rightmost untrusted entry
of X-Forwarded-For and strips forwarding headers from peers that are not proxies, so a
per-IP limit is per person rather than per proxy. Checked rather than assumed.

What is not

Capture is the one that matters. /jobs/captures/new/ makes Postulo itself issue an
outbound request to an address the caller supplies. check_destination refuses private
addresses and revalidates on redirect -- the audit probed loopback, 169.254.169.254,
[::1], 10.0.0.0/8, file:// and gopher://, and every one was refused, so this is not
SSRF. What is missing is a bound on how often. One account can ask the instance to fetch
as fast as it can, which turns somebody's self-hosted box into a modest scanner or exhausts
its own outbound connections.

The API applies no limit of its own. api/auth.py looks a token up by hash and returns
it; there is no throttle at any layer. A token is 32 bytes and hashed at rest, so this is
about a valid token rather than a guessed one.

/logs and /metrics are token-guarded -- hmac.compare_digest, checked -- and
unbounded.

What would fix it

A limit keyed on the account rather than the address, since all of these require one, and a
number an operator can raise: an instance with three people has different needs from one
with three hundred. Capture deserves the tightest, because it is the only one that makes the
server talk to somebody else's.

Worth deciding whether to add a dependency or write it against the cache that already backs
allauth's limits. The second is a few lines and brings no new supply chain.

Classification

Security, enhancement. Tier 2: no data is at risk, and an instance can be made to work very
hard by anybody holding an account on it.

## Found by A security audit. Not reachable by a stranger -- every surface here needs an account or a token -- but nothing bounds what an account may do once it has one. ## What is already protected allauth's own limits are in force with a cache behind them, so authentication is covered: ``` login 30/m/ip login_failed 10/m/ip, 5/300s/key signup 20/m/ip reset_password 20/m/ip, 5/m/key ``` `TrustedProxyMiddleware` also gets the key right: it takes the **rightmost untrusted** entry of `X-Forwarded-For` and strips forwarding headers from peers that are not proxies, so a per-IP limit is per person rather than per proxy. Checked rather than assumed. ## What is not **Capture is the one that matters.** `/jobs/captures/new/` makes **Postulo itself** issue an outbound request to an address the caller supplies. `check_destination` refuses private addresses and revalidates on redirect -- the audit probed loopback, `169.254.169.254`, `[::1]`, `10.0.0.0/8`, `file://` and `gopher://`, and every one was refused, so this is not SSRF. What is missing is a bound on **how often**. One account can ask the instance to fetch as fast as it can, which turns somebody's self-hosted box into a modest scanner or exhausts its own outbound connections. **The API applies no limit of its own.** `api/auth.py` looks a token up by hash and returns it; there is no throttle at any layer. A token is 32 bytes and hashed at rest, so this is about a valid token rather than a guessed one. **`/logs` and `/metrics`** are token-guarded -- `hmac.compare_digest`, checked -- and unbounded. ## What would fix it A limit keyed on the account rather than the address, since all of these require one, and a number an operator can raise: an instance with three people has different needs from one with three hundred. Capture deserves the tightest, because it is the only one that makes the server talk to somebody else's. Worth deciding whether to add a dependency or write it against the cache that already backs allauth's limits. The second is a few lines and brings no new supply chain. ## Classification Security, enhancement. Tier 2: no data is at risk, and an instance can be made to work very hard by anybody holding an account on it.
tiagoagueda added this to the 0.3.0 milestone 2026-09-07 18:54:29 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#112
No description provided.