Rate limit C2S outbox posts and emoji reactions #8042

Merged
lambadalambda merged 3 commits from lambadalambda/pleroma:fix/c2s-rate-limit into develop 2026-10-31 16:22:01 +00:00
  • POST /users/:nickname/outbox (AP C2S) now uses the statuses_actions limiter. RateLimiter keys buckets by limiter name and user, so the outbox shares one counter with the Mastodon API status actions.
  • Emoji reaction create/delete (/api/v1/pleroma/statuses/:id/reactions/:emoji) now use statuses_actions plus a per-status status_id_action:emoji_react bucket, like favourite and reblog.

Not covered: media uploads (AP upload_media, Mastodon media endpoints).

- `POST /users/:nickname/outbox` (AP C2S) now uses the `statuses_actions` limiter. RateLimiter keys buckets by limiter name and user, so the outbox shares one counter with the Mastodon API status actions. - Emoji reaction create/delete (`/api/v1/pleroma/statuses/:id/reactions/:emoji`) now use `statuses_actions` plus a per-status `status_id_action:emoji_react` bucket, like favourite and reblog. Not covered: media uploads (AP `upload_media`, Mastodon media endpoints).
POST /users/:nickname/outbox had no rate limiter, so a client could send
unlimited activities through the C2S API. Apply :statuses_actions to
update_outbox. RateLimiter keys buckets by limiter name and user, so the
outbox shares one counter with the Mastodon API status actions and mixing
both APIs does not double the limit.
The emoji reaction create and delete endpoints had no rate limiter.
Apply :statuses_actions like the other status actions, plus a per-status
:status_id_action bucket ("status_id_action:emoji_react") in the same way
favourite and reblog do.
chore: add changelog entry for C2S rate limits
All checks were successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/unit-testing-elixir-1.19 Pipeline was successful
ci/woodpecker/pr/unit-testing-elixir-1.17 Pipeline was successful
cdc741f3ce
Author
Owner

/oc review

/oc review
opencode-fix-bot requested changes 2026-10-31 07:46:28 +00:00
Dismissed
opencode-fix-bot left a comment

opencode-agent:job=145:generation=1

Merge readiness: 3/5 (capped by one P1 below)

P1 — Rebase onto current develop before merge, or favourite/reblog per-status limiting regresses to per-user

Base develop already contains #8043 (53b79781d), which fixed StatusController to use params: ["id"] because that controller keeps string param keys (CastAndValidate, replace_params: false), so params: [:id] never matches and status_id_action collapses to a single bucket per user. This branch forked before that fix, so the base-to-head diff shows ["id"] -> [:id] on both plugs (status_id_action:reblog_unreblog, status_id_action:fav_unfav) and drops the per-status test block. Merging as-is reintroduces the bug: after a few favourites/reblogs within the status_id_action window across distinct statuses, legitimate users get 429 Throttled, because the limit is keyed per user instead of per user+status.

Triggering condition: any user favouriting/reblogging more distinct statuses than the status_id_action limit within one window.

Smallest correction: rebase onto current develop, keep params: ["id"] in lib/pleroma/web/mastodon_api/controllers/status_controller.ex (both plugs), and retain the status_id_action rate limit test block.

Checked, no findings

  • EmojiReactionController using params: [:id] is correct: that controller uses default CastAndValidate (atom param keys), unlike StatusController. Its status_id_action:emoji_react bucket is distinct from the fav/reblog buckets, and plug order (per-status before global, both after auth/scope checks) matches the existing pattern.
  • The C2S update_outbox plug sits after authentication and scope checks and keys by the authenticated user, so posting to another nickname outbox still counts against the caller; sharing the statuses_actions bucket with the Mastodon API is as described.

No other concrete defects in this diff.

opencode-agent:job=145:generation=1 ## Merge readiness: 3/5 (capped by one P1 below) ### P1 — Rebase onto current develop before merge, or favourite/reblog per-status limiting regresses to per-user Base develop already contains #8043 (53b79781d), which fixed StatusController to use params: ["id"] because that controller keeps string param keys (CastAndValidate, replace_params: false), so params: [:id] never matches and status_id_action collapses to a single bucket per user. This branch forked before that fix, so the base-to-head diff shows ["id"] -> [:id] on both plugs (status_id_action:reblog_unreblog, status_id_action:fav_unfav) and drops the per-status test block. Merging as-is reintroduces the bug: after a few favourites/reblogs within the status_id_action window across distinct statuses, legitimate users get 429 Throttled, because the limit is keyed per user instead of per user+status. Triggering condition: any user favouriting/reblogging more distinct statuses than the status_id_action limit within one window. Smallest correction: rebase onto current develop, keep params: ["id"] in lib/pleroma/web/mastodon_api/controllers/status_controller.ex (both plugs), and retain the status_id_action rate limit test block. ### Checked, no findings - EmojiReactionController using params: [:id] is correct: that controller uses default CastAndValidate (atom param keys), unlike StatusController. Its status_id_action:emoji_react bucket is distinct from the fav/reblog buckets, and plug order (per-status before global, both after auth/scope checks) matches the existing pattern. - The C2S update_outbox plug sits after authentication and scope checks and keys by the authenticated user, so posting to another nickname outbox still counts against the caller; sharing the statuses_actions bucket with the Mastodon API is as described. No other concrete defects in this diff.
lambadalambda force-pushed fix/c2s-rate-limit from cdc741f3ce
All checks were successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/unit-testing-elixir-1.19 Pipeline was successful
ci/woodpecker/pr/unit-testing-elixir-1.17 Pipeline was successful
to 924df2a063
All checks were successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/unit-testing-elixir-1.17 Pipeline was successful
ci/woodpecker/pr/unit-testing-elixir-1.19 Pipeline was successful
2026-10-31 07:52:10 +00:00
Compare
Author
Owner

/oc review

/oc review
opencode-fix-bot left a comment

opencode-agent:job=146:generation=1

Merge readiness: 5/5 — production-ready, no findings

Prior REQUEST_CHANGES (job=145) P1 is addressed in this head: the branch no longer touches lib/pleroma/web/mastodon_api/controllers/status_controller.ex, and the base already contains #8043 (53b79781d) with params: ["id"]. No per-status regression remains.

Checked, no findings:

  • C2S update_outbox plug sits after authentication and scope checks and uses name: :statuses_actions with no params, so it keys per-user and shares one counter with Mastodon API status actions as described. Forbidden nickname-mismatch attempts still count, so there is no bypass by posting to another nickname outbox.
  • EmojiReactionController plugs match the established pattern: per-status status_id_action:emoji_react before global statuses_actions, both after CastAndValidate and scope checks. params: [:id] is correct here because this controller uses default CastAndValidate (atom keys), unlike StatusController with replace_params: false. The emoji_react bucket is distinct from fav/reblog buckets.
  • Tests cover per-status isolation (distinct statuses not throttled) and global statuses_actions sharing on both surfaces, plus C2S/Mastodon API sharing.

No concrete actionable defects introduced by this pull request.

opencode-agent:job=146:generation=1 ## Merge readiness: 5/5 — production-ready, no findings Prior REQUEST_CHANGES (job=145) P1 is addressed in this head: the branch no longer touches lib/pleroma/web/mastodon_api/controllers/status_controller.ex, and the base already contains #8043 (53b79781d) with params: ["id"]. No per-status regression remains. Checked, no findings: - C2S update_outbox plug sits after authentication and scope checks and uses name: :statuses_actions with no params, so it keys per-user and shares one counter with Mastodon API status actions as described. Forbidden nickname-mismatch attempts still count, so there is no bypass by posting to another nickname outbox. - EmojiReactionController plugs match the established pattern: per-status status_id_action:emoji_react before global statuses_actions, both after CastAndValidate and scope checks. params: [:id] is correct here because this controller uses default CastAndValidate (atom keys), unlike StatusController with replace_params: false. The emoji_react bucket is distinct from fav/reblog buckets. - Tests cover per-status isolation (distinct statuses not throttled) and global statuses_actions sharing on both surfaces, plus C2S/Mastodon API sharing. No concrete actionable defects introduced by this pull request.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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
pleroma/pleroma!8042
No description provided.