Repository navigation
fix(core): apply webfetch URL rules to redirect targets - #59748
Conversation
There was a problem hiding this comment.
Looks good. A redirect to a URL the rules deny is followed on the base and blocked with this change.
I couldn't run it through the TUI because the simulation backend blocks all outbound HTTP. Instead I ran a temporary test with the real fetch client and a local server where /redirect returns 302 to a denied /secret. On the base, webfetch returned the secret body. With this PR, it failed with "Permission denied: webfetch". Managed tool.use deny policies also apply to redirects, since they run through the same evaluate hook. The three test files pass (200 tests). The source type-checks cleanly with tsc; the package typecheck script can't run here because bun check isn't available.
Public API unchanged: Permission.Interface.assertNotDenied is internal to core, and the plugin, SDK, HTTP API and config are untouched.
No blocking concerns.
Related: #55232
) Port redirect denial into the current V2 built-in and separate V1 adapter. The V2 deny-only check uses the explicit invocation agent; V1 binds the merged agent/session rules through SessionTools. Resolve and deny-check each HTTP(S) redirect before transport; allow and ask-only destinations continue without a new prompt. Preserve both adapters' existing output contracts and Cloudflare retry behavior. Canonical URL identity remains the next donor's scope. (cherry picked from commit 17837a2)
* chore(core): plan webfetch URL policy backport 1. Cherry-pick the redirect-denial donor first and adapt deny-only checks to the current V2 permission service and the V1 session-bound policy path. 2. Cherry-pick the canonicalization donor second; use parsed HTTP(S) serialization for policy and transport in both adapters, while leaving Code Mode out. 3. Add executor-driven regressions for canonical allow/deny, redirect targets and chains, ask-only destinations, unsupported URLs, and live transport request evidence; demonstrate focused red/green absence proofs. 4. Record donor provenance, compatibility adaptations, fork paths, and executed focused results in CHANGESET.md, then run the required crew gates. * fix(core): apply webfetch URL rules to redirect targets (anomalyco#59748) Port redirect denial into the current V2 built-in and separate V1 adapter. The V2 deny-only check uses the explicit invocation agent; V1 binds the merged agent/session rules through SessionTools. Resolve and deny-check each HTTP(S) redirect before transport; allow and ask-only destinations continue without a new prompt. Preserve both adapters' existing output contracts and Cloudflare retry behavior. Canonical URL identity remains the next donor's scope. (cherry picked from commit 17837a2) * fix(core): match web permissions against the URL that is requested (anomalyco#60505) Port canonical URL identity into the fork's existing V2 and V1 webfetch owners, using the parsed HTTP(S) href for initial policy matching and transport. Redirect targets remain resolved and checked by their serialized URL. Preserve the V1 image attachment and V2 unsupported-attachment contracts. This is a partial port of anomalyco#60505: exclude its packages/core/src/codemode/fetch.ts changes because that Code Mode API is not a drop-in to this fork's current path. Adapted owners: packages/core/src/tool/webfetch.ts and packages/opencode/src/tool/webfetch.ts. (cherry picked from commit 805a4b8) * docs(opencode): record webfetch URL policy backport * fix(webfetch): cancel discarded response bodies Cancel unread redirect and rejected-response bodies in the V1 and V2 executors, with live streaming regressions for release paths. Record the adoption split as partly upstream. Claude-Session: ses_e5cdbcb11ffeK5x75lZZqBWCmy * docs(opencode): record webfetch response release proofs Claude-Session: ses_e5cdbcb11ffeK5x75lZZqBWCmy --------- Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Adapt anomalyco/opencode 17837a2 (anomalyco#59748), 125655e (anomalyco#61701), and e803323 (anomalyco#62373). Use this fork's existing permission assertions per redirect; reject URL credentials, ignore fragments, and bound redirects. Prove denied destinations receive no HTTP request.
Problem
webfetch checked the URL rules for the URL the model asked for, then let fetch follow redirects on its own. So a
webfetchdeny rule (or a policy that only allows certain hosts) didn't apply to wherever the page redirected.Change
webfetch now follows redirects itself and checks each new URL against the rules first. It never prompts for a redirect, it only stops when a rule denies the target.
Permission.assertNotDenied: same evaluation asassert(config, session rules, saved approvals, plugin hooks) but it never asks, it only fails on deny.Out of scope
A redirect to a URL that would normally ask is still followed without asking.
Verification
bun test test/permission.test.ts test/tool-webfetch.test.ts test/plugin/permission-assert.test.tsinpackages/core: 200 passbun run checkfrom the repo root: passesAdded by the opencode bot (AI-generated).
Related: #55232