Skip to content

fix(gateway): cap inbound media download size to prevent memory exhaustion - #50321

Merged
teknium1 merged 1 commit into
mainfrom
fix/13145-inbound-media-cap
Jun 21, 2026
Merged

fix(gateway): cap inbound media download size to prevent memory exhaustion#50321
teknium1 merged 1 commit into
mainfrom
fix/13145-inbound-media-cap

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Inbound image/audio/video can no longer OOM the gateway — payloads are now size-capped before they're buffered into memory. Fixes #13145.

Inbound media was read fully into process RAM before being written to the cache, with no limit. A large upload (Discord Nitro allows 500 MB) or a remote media URL pointing at a huge file could spike memory and OOM-kill the gateway on constrained deployments.

Changes

  • gateway/platforms/base.py: validate_inbound_media_size() + get_inbound_media_max_bytes(); guard cache_image/audio/video_from_bytes; stream cache_image/audio_from_url (reject on oversized Content-Length, re-check running total per chunk).
  • plugins/platforms/discord/adapter.py: _read_attachment_bytes checks att.size up front — oversized attachments rejected before any bytes hit memory.
  • hermes_cli/config.py: new gateway.max_inbound_media_bytes (default 128 MiB; 0 disables). No new env var — non-secret config stays in config.yaml.
  • Tests: new TestInboundMediaSizeCap + migrated the existing retry/SSRF tests to the streaming API.

Validation

Path Behavior
cache_*_from_bytes over cap (image/audio/video) raises ValueError, nothing written
cache_*_from_url oversized (Content-Length or chunked) rejected mid-stream
Discord oversized attachment att.read() never called
Under cap / cap=0 unchanged passthrough

tests/gateway/test_platform_base.py 157✓, test_media_download_retry.py 36✓, test_discord_* 34✓. E2E verified against a live local HTTP server (streaming + Content-Length paths) on a temp HERMES_HOME.

Credit

Salvaged and extended from @sgaofen's #13341 (original report + shared-helper approach). Reapplied onto current main (Discord adapter has since moved to plugins/), moved the knob from an env var to config.yaml, and added the video cache guard #13341 missed. Closes the cluster: also supersedes #42931 (@youngstar-eth), #29430 (@rayasa07), #35297 (@zapabob).

Infographic

inbound-media-size-cap

…stion

Inbound image/audio/video payloads were buffered fully into process memory
before being written to the cache, with no size limit. A large upload
(Discord Nitro allows 500 MB) or a remote media URL in an inbound message
pointing at a huge file could spike RAM and OOM-kill the gateway.

Enforce a configurable cap in the shared cache helpers (gateway/platforms/
base.py) so the protection holds across every platform adapter, not one:

- cache_image/audio/video_from_bytes reject oversized payloads before writing
  (video was the gap in the original report — now covered).
- cache_image/audio_from_url stream the body, rejecting on an oversized
  Content-Length header and re-checking the running total per chunk so an
  absent/lying header can't smuggle an unbounded body past the cap.
- Discord's _read_attachment_bytes checks att.size up front, so an oversized
  attachment is rejected before any bytes are pulled into memory.

Configurable via gateway.max_inbound_media_bytes in config.yaml (default
128 MiB; 0 disables). No new env var — non-secret config lives in config.yaml.

Salvaged and extended from @sgaofen's PR #13341 (the original report and the
shared-helper approach). Reapplied onto current main (Discord adapter has
since moved to plugins/platforms/discord/), the configurable knob moved from
an env var to config.yaml, and the video cache helper added.

Co-authored-by: Hermes Agent <noreply@nousresearch.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: fix/13145-inbound-media-cap vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11313 on HEAD, 11312 on base (🆕 +1)

🆕 New issues (1):

Rule Count
not-subscriptable 1
First entries
tests/gateway/test_media_download_retry.py:74: [not-subscriptable] not-subscriptable: Cannot subscript object of type `None` with no `__getitem__` method

✅ Fixed issues: none

Unchanged: 5929 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/security Security vulnerability or hardening P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/discord Discord bot adapter labels Jun 21, 2026
@teknium1
teknium1 merged commit 93ea9b0 into main Jun 21, 2026
35 checks passed
@teknium1
teknium1 deleted the fix/13145-inbound-media-cap branch June 21, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/discord Discord bot adapter type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No size cap on inbound media downloads (image/audio/video) — potential memory exhaustion

3 participants