Skip to content

fix(media): constrain attachment redirects#134

Merged
steipete merged 2 commits into
mainfrom
agent/harden-attachment-redirects
Jul 17, 2026
Merged

fix(media): constrain attachment redirects#134
steipete merged 2 commits into
mainfrom
agent/harden-attachment-redirects

Conversation

@steipete

Copy link
Copy Markdown
Collaborator

Summary

  • Reject default-client redirects after three hops or when the destination leaves Discrawl's existing Discord CDN allowlist.
  • Revalidate the final response URL even when callers inject an HTTP client with a different redirect policy.
  • Add regression coverage for an allowed initial Discord CDN URL whose final response points at a link-local metadata endpoint.

Extracted from the generic attachment hardening in #131. Thanks @GrantTheAssistant; contributor credit is preserved in the commit and changelog.

Proof

  • go test ./... -count=1
  • go test -count=1 -race ./internal/media
  • golangci-lint run ./...
  • go vet ./...
  • staticcheck ./...
  • gosec -exclude=G101,G115,G202,G301,G304 ./...
  • govulncheck ./...
  • go mod verify
  • go mod tidy -diff

The integration regression drives the production fetchURL boundary through a real http.Client/RoundTripper: an allowlisted Discord request returns a final link-local response URL, and Discrawl rejects it before reading or writing the response body.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75d6dc51f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/media/cache.go
Comment on lines +141 to +142
if len(via) >= 3 || !isAllowedAttachmentURL(req.URL.String()) {
return errors.New("attachment redirect denied")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow the third redirect before denying

If an attachment legitimately needs three allowed Discord CDN redirects, this guard fails it before issuing the third redirected request: net/http calls CheckRedirect with req as the upcoming request and via as the requests already made, so len(via) == 3 is the third redirect target (initial request plus two followed redirects), not a request after three redirect hops. This caps successful fetches at two redirects and records those otherwise-allowed attachments as failures.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Jul 17, 2026
@clawsweeper

clawsweeper Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed July 17, 2026, 3:37 AM ET / 07:37 UTC.

Summary
The PR wraps attachment HTTP clients with Discord CDN redirect enforcement, revalidates final response URLs, and adds redirect regression coverage.

Reproducibility: yes. for the proposed-patch defect: three consecutive allowlisted redirects reach CheckRedirect with len(via) == 3, and the third target is denied before its request is issued.

Review metrics: 2 noteworthy metrics.

  • Redirect contract: 2 hops implemented, 3 promised. The off-by-one mismatch is the concrete correctness blocker.
  • Patch scope: 3 files, +86/-3. The focused scope makes the blocker suitable for a narrow repair and re-review.

Root-cause cluster
Relationship: canonical
Canonical: #134
Summary: This PR is the focused canonical landing candidate for attachment redirect hardening extracted from the closed broad proposal.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🌊 off-meta tidepool
Patch quality: 🦐 gold shrimp
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Fix and test the exact three-hop and fourth-hop boundary.
  • Refresh the branch against current main and re-review the resulting merge.

Risk before merge

  • [P1] Exactly three allowlisted Discord CDN redirects would be rejected and recorded as attachment-fetch failures, contrary to the PR's stated policy.
  • [P1] The branch is currently dirty against newer main, so its final three-way merge result has not been validated.

Maintainer options:

  1. Correct the redirect boundary (recommended)
    Allow exactly three allowlisted hops, reject the fourth, add focused boundary tests, and refresh the branch against current main.
  2. Adopt a two-hop policy
    If two hops are intentional, revise the stated contract and document the accepted attachment-compatibility impact before landing.
  3. Pause the hardening
    Pause or close the PR if the intended compatibility boundary cannot be established safely.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Correct the redirect limit so exactly three allowlisted hops succeed and the fourth is denied; add focused three-hop and fourth-hop boundary tests, then adapt the branch cleanly to current main without weakening final-response URL validation.

Next step before merge

  • [P2] The blocker is a narrow mechanical hop-count repair with focused boundary coverage, followed by a current-main refresh.

Security
Cleared: The diff strengthens an SSRF-sensitive URL boundary without adding dependency, permission, secret, or supply-chain exposure; the identified blocker is compatibility rather than weakened security.

Review findings

  • [P2] Allow the promised third redirect hop — internal/media/cache.go:139
Review details

Best possible solution:

Preserve the CDN allowlist and final-response defense, allow exactly three redirect hops, reject the fourth, add exact boundary coverage, and revalidate the refreshed branch against current main.

Do we have a high-confidence way to reproduce the issue?

Yes for the proposed-patch defect: three consecutive allowlisted redirects reach CheckRedirect with len(via) == 3, and the third target is denied before its request is issued.

Is this the best way to solve the issue?

No as written; the defense-in-depth structure is appropriate, but the hop-count guard must match the stated three-hop contract and be revalidated after updating the branch.

Full review comments:

  • [P2] Allow the promised third redirect hop — internal/media/cache.go:139
    Change this guard so three allowlisted redirect hops succeed and the fourth is denied. net/http calls CheckRedirect for the upcoming request with prior requests in via, so len(via) >= 3 blocks the third redirect target and contradicts the PR's stated three-hop limit.
    Confidence: 0.99

Overall correctness: patch is incorrect
Overall confidence: 0.97

AGENTS.md: unclear because the file could not be read completely.

Codex review notes: model internal, reasoning high; reviewed against 91aa80d2f826.

Label changes

Label changes:

  • add P2: This is a focused security improvement with a limited but concrete attachment compatibility regression in the proposed patch.
  • add merge-risk: 🚨 compatibility: Merging as written can fail existing attachment downloads that require exactly three allowed Discord CDN redirects.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🌊 off-meta tidepool and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Not applicable: The author is a repository collaborator, so the external-contributor real-behavior proof gate does not apply; the reported integration and security checks remain useful supplemental evidence.

Label justifications:

  • P2: This is a focused security improvement with a limited but concrete attachment compatibility regression in the proposed patch.
  • merge-risk: 🚨 compatibility: Merging as written can fail existing attachment downloads that require exactly three allowed Discord CDN redirects.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🌊 off-meta tidepool and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Not applicable: The author is a repository collaborator, so the external-contributor real-behavior proof gate does not apply; the reported integration and security checks remain useful supplemental evidence.
Evidence reviewed

Acceptance criteria:

  • [P1] go test -count=1 -race ./internal/media.
  • [P1] go test ./... -count=1.
  • [P1] go vet ./...

What I checked:

  • Redirect boundary defect: The branch checks len(via) >= 3; when Go evaluates the third redirect target, via already contains the initial request and two redirected requests, so the target is denied before the promised third hop. (internal/media/cache.go:139, 75d6dc51f3e4)
  • Stated redirect contract: The PR summary says redirects should be rejected after three hops, which conflicts with the implemented two-hop maximum. (internal/media/cache.go:139, 75d6dc51f3e4)
  • Established attachment workflow: Attachment media caching is an existing shipped Discrawl capability, so an overly strict redirect boundary can regress real attachment downloads. (CHANGELOG.md:132, 91aa80d2f826)
  • Current-main necessity: The supplied current-main state does not contain this PR commit or identify a merged replacement, while the source proposal was closed unmerged; the focused hardening therefore remains useful. (internal/media/cache.go:131, 91aa80d2f826)
  • Branch integration state: GitHub reports the current head as not mergeable with mergeable state dirty against a newer main, despite successful checks. (75d6dc51f3e4)

Likely related people:

  • steipete: Repository history identifies Peter Steinberger as a central Discrawl contributor, and he authored the focused media-hardening commit under review. (role: feature owner and recent area contributor; confidence: high; commits: 75d6dc51f3e4; files: internal/media/cache.go, internal/media/cache_test.go, CHANGELOG.md)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Co-authored-by: Brendan <brendan@grant.lan>
@steipete
steipete force-pushed the agent/harden-attachment-redirects branch from 75d6dc5 to ea55e2a Compare July 17, 2026 07:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ea55e2a626

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/media/cache.go
defer func() { _ = resp.Body.Close() }()
// Injected clients can supply their own redirect policy, so validate the
// final response URL at the fetch boundary as well.
if resp.Request == nil || resp.Request.URL == nil || !isAllowedAttachmentURL(resp.Request.URL.String()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve injected RoundTrippers that omit Response.Request

When FetchOptions.HTTPClient is backed by a custom RoundTripper, net/http does not populate Response.Request for the transport; fakes and test transports often omit it. This guard now turns an otherwise successful response for the already-validated attachment URL into attachment response URL denied before status/body handling whenever the transport leaves Request nil, so injected clients that worked before fail even with no redirect involved.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 05f4d78 into main Jul 17, 2026
10 checks passed
@steipete
steipete deleted the agent/harden-attachment-redirects branch July 17, 2026 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant