feat(channels/discord): outbound attachments with SSRF-guarded multipart batching#1229
Open
benhoverter wants to merge 1 commit into
Open
feat(channels/discord): outbound attachments with SSRF-guarded multipart batching#1229benhoverter wants to merge 1 commit into
benhoverter wants to merge 1 commit into
Conversation
…art batching Squashed reroll of topic/discord-outbound-attachments onto v0.6.9 (acf2587). Replaces the 16-commit history that landed via local-main merge 2c32102. Reroll context: - mime/size on ChannelContent::File and file:// in download_image_to_blocks already landed upstream via RightNow-AI#1143; dropped from this branch. - source_url-on-ContentBlock::Image is provided by feat/runtime-image-cache (PR RightNow-AI#1151); the three compactor source_url tests live there. - Net contribution here is the outbound Multipart feature plus its deps. Feature scope: - Outbound for ChannelContent::File and Image URL arms (per-block resolver dispatch in mixed Multipart payloads). - N-attachment batching with greedy-pack chunker and aggregate per-chunk byte cap; parallel attachment fetch via try_join_all. - SSRF guard + redirect revalidation + log scrubbing for URL fetch (Fetcher trait abstraction). - Body-aware 429 retry on the multi-file path; structured partial-send WARN with grep-friendly event. - Transparent decompression disabled on image download (bridge.rs). Verification: - cargo check --workspace: clean - cargo test -p openfang-channels -p openfang-runtime: 1002/1002 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lets agents send images and files outbound through the Discord channel.
This is a deliberately narrowed re-submission of the transport half of #1162
(closed 2026-05-12) — see "How this addresses the #1162 review" below.
Scope is exactly one file of production code (
crates/openfang-channels/src/discord.rs):multipart/form-data, with greedy byte-cap packing for Discord's 25 MiB / 10-filelimits.
ChannelContent::FileData(pre-resolved bytes), andFile/Imageblocks carrying URLs.
(loopback, RFC1918, link-local, unique-local, multicast, unspecified, CGNAT),
IPv4-mapped IPv6, and redirect revalidation at each hop (
URL_FETCH_MAX_REDIRECTS).emits a structured, grep-friendly WARN.
Fetchertrait extracted so tests can stub the wire without disabling the guard.How this addresses the #1162 review
#1162 was closed for two blockers. This branch resolves both.
/cc @jaberjaber23 — you reviewed the original #1162; flagging so you can see both
blockers you raised are addressed below.
Credential-exfil via too-wide
allow_roots— the blocking concern. Notpresent in this branch. This PR contains no
<openfang:attach path=…>parser,no
default_allow_roots/allow_rootlogic, and no local filesystem reads at all(
fs::read/File::open/tokio::fs— none). It transports pre-resolved bytes(
FileData) and SSRF-guarded URLs only. The local-path attach surface that thereviewer flagged is intentionally out of scope here and will land separately
with
allow_rootsempty-by-default + a hard~/.openfang/refusal, as requested.This matches the reviewer's own suggested split ("PR A: smaller, easier review").
Dirty against
main(fix(channels/discord): surface image attachments to text-only providers #1143/feat(channels): harden channel_id binding — adapter allowlist, strict validation, single source of truth for routing #1147/runtime/claude_code: materialize image blocks to tmpfile + extract image_cache module #1151/Unintended behavior of OpenFang workspaces #1097 drift) — resolved. Branch isrebased on current
upstream/main;git merge-treeis clean. It builds on the#1143Multipart types already inmain(no duplicate variant) and carriesno parallel
image_cacheimplementation.Out of scope (intentional)
<openfang:attach path=…>) outbound attachment +allow_rootsgating— separate PR, with the security hardening the feat(channels/discord) Outbound file/image attachments + image_cache hardening #1162 review required.
Test plan
Tests live in-file (
discord.rstest module):[::1],IPv4-mapped metadata (
[::ffff:169.254.169.254]); allows public IP literal;asserts errors never leak the query string.
mixed Text/Image/File dispatch, empty-multipart rejection, body-aware 429 on the
aggregated path, SSRF-blocked
FileURL surfacessend() -> Err.ssrf_bypassfetcher so the production guard stays enforced everywhere else.
Run before submit:
cargo test -p openfang-channels,cargo clippy -p openfang-channels --all-targets -- -D warnings.Note for review
The
Fetchertrait + SSRF guard is the security-sensitive surface — flagging it forcloser review (same as #1162; that engineering was praised, only the path-attach
allow-root sank the prior PR, and it isn't here).