Skip to content

Prevent XSS via unsafe href protocols in WebFetchToolCall#3403

Merged
ThomasK33 merged 2 commits into
mainfrom
claude/autopatch-scan-18aa40f9-vuln-1275269-visAU
May 28, 2026
Merged

Prevent XSS via unsafe href protocols in WebFetchToolCall#3403
ThomasK33 merged 2 commits into
mainfrom
claude/autopatch-scan-18aa40f9-vuln-1275269-visAU

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Add URL validation to prevent javascript: and other unsafe protocol XSS attacks when rendering fetched URLs as clickable links in the WebFetchToolCall component.

Changes

  • New isSafeHref() function: Validates that URLs use only http: or https: protocols before rendering as clickable <a> tags. Uses URL constructor for robust parsing and safely catches malformed URLs.
  • Conditional link rendering: URLs that fail the safety check are now rendered as plain <span> elements instead of clickable links, preventing potential XSS while still displaying the URL text.

Implementation Details

The validation happens at render time in the URL display section. Safe URLs render as before with full link functionality; unsafe URLs (including malformed ones) degrade gracefully to plain text, maintaining readability without the security risk.

https://claude.ai/code/session_015vY8c55jcKPoPNKm2264uC

The web_fetch tool call component rendered args.url directly into an
<a href=...> without protocol validation. Since Zod's z.string().url()
accepts javascript: URLs, a prompt-injected tool call could produce a
clickable link that executes arbitrary JS in the renderer.

Add isSafeHref() that allowlists http:/https: protocols. Non-safe URLs
render as plain text instead of clickable links.

https://claude.ai/code/session_015vY8c55jcKPoPNKm2264uC
@ThomasK33 ThomasK33 marked this pull request as ready for review May 27, 2026 15:11
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented May 27, 2026

Copy link
Copy Markdown

Chat: Review posted | View chat
Requested: 2026-05-28 09:12 UTC by @ThomasK33
Spend: $19.08 / $100.00

Review history
  • R1 (2026-05-27): 15 reviewers, 1 Nit, 1 P2, 2 P3, COMMENT. Review
  • R2 (2026-05-28), 1 Nit, 1 P2, 2 P3, COMMENT. Review

deep-review v0.5.0 | Round 2 | 5d79d9b..62ae660

Last posted: Round 2, 4 findings (1 P2, 2 P3, 1 Nit), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Open WebFetchToolCall.tsx:41 Security utility isSafeHref has no test coverage R1 Netero P3, Mafu-san P2 Yes
CRF-2 P2 Open WebFetchToolCall.tsx:41 File-local function duplicates browserUrl.ts allowlist; sibling XSS at WorkspaceStatusIndicator.tsx:77 unpatched R1 Hisoka P2, Meruem P3, Robin P3, Nami Nit, Gon Nit, Leorio Note Yes
CRF-3 P3 Open WebFetchToolCall.tsx:41 Tool schema z.string().url() accepts unsafe protocols despite describing "http or https" R1 Pariston Yes
CRF-4 Nit Author fixed (62ae660) WebFetchToolCall.tsx:38 Doc comment restates code and narrows threat model to one vector R1 Gon P2, Leorio Nit Yes

Contested and acknowledged

(none)

Round log

Round 1

Panel (15 reviewers). 1 P2, 2 P3, 1 Nit. Reviewed against 5d79d9b..bc5e16e.
Netero: 1 P3 (CRF-1). Panel convergence on extraction/DRY (6 reviewers). Sibling vulnerability verified at WorkspaceStatusIndicator.tsx:77. 7 reviewers confirmed isSafeHref implementation is correct.

Round 2

BLOCKED. CRF-4 addressed (62ae660). CRF-1, CRF-2, CRF-3 silent. No review.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Kurapika security
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

ℹ️ 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".

@coder-agents-review coder-agents-review 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.

Correct fix. The allowlist approach, URL constructor normalization, and fail-closed error handling are all the right calls. Graceful degradation to plain text preserves readability without the click target. The commit message is excellent: names the vulnerability, vector, and component in one subject line.

Seven reviewers independently verified that isSafeHref correctly blocks javascript:, data:, vbscript:, blob:, file:, mixed-case, and malformed URLs. Kurapika confirmed that javascript: href execution is inline (not a navigation event), so the renderer-level fix is the correct primary defense even with Electron's setWindowOpenHandler in place.

The main concern is scope. Six reviewers converged on the same structural issue: isSafeHref duplicates the protocol allowlist already in browserUrl.ts, and its file-local placement leaves a verified sibling vulnerability at WorkspaceStatusIndicator.tsx:77 unprotected. Extracting the function resolves the duplication, the sibling, and the missing test coverage in one move.

1 P2 (extract to shared utility + sibling XSS), 2 P3 (test coverage, schema validation gap), 1 Nit (comment).

"A security utility that only one caller can reach is a security utility that rots." Hisoka

🤖 This review was automatically generated with Coder Agents.

/**
* Only allow http/https URLs as clickable hrefs to prevent javascript: XSS.
*/
function isSafeHref(url: string): boolean {

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 [CRF-2] This function duplicates the protocol allowlist already defined in browserUrl.ts (ALLOWED_BROWSER_URL_PROTOCOLS: new Set(["http:", "https:"])) and the same new URL().protocol check in normalizeBrowserUrl. The existing utility has full test coverage in browserUrl.test.ts covering javascript:, data:, file:, and vbscript:.

More importantly, the file-local placement means WorkspaceStatusIndicator.tsx:77 renders agentStatus.url as a clickable <a href> with no protocol validation. That URL flows from AgentStatusSchema (z.string().optional(), no protocol check) and carries the same threat model. In browser mode (no Electron guards), clicking a javascript: href there executes in the page context.

Extract a boolean predicate to browserUrl.ts that reuses ALLOWED_BROWSER_URL_PROTOCOLS:

export function isSafeHref(url: string): boolean {
  try {
    return ALLOWED_BROWSER_URL_PROTOCOLS.has(new URL(url).protocol);
  } catch {
    return false;
  }
}

Then apply it here and at WorkspaceStatusIndicator.tsx:77. The thin wrapper inherits existing test coverage; only the boolean interface needs a new test. GovernorSection.tsx:103 hand-rolls the same check and could also consume this. (Hisoka P2, Meruem P3, Robin P3, Nami, Gon, Leorio)

🤖

/**
* Only allow http/https URLs as clickable hrefs to prevent javascript: XSS.
*/
function isSafeHref(url: string): boolean {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 [CRF-1] No test coverage for a security boundary function. The function is correct (verified by 7 reviewers against javascript:, data:, vbscript:, mixed-case, malformed URLs), but visual inspection is not regression protection. If CRF-2 is adopted and this moves to browserUrl.ts, the existing test suite covers the core logic and only the boolean wrapper needs a test. If it stays here, it is pure logic (no DOM, no React state) and testable with a plain vitest file. (Netero, Mafu-san)

🤖

/**
* Only allow http/https URLs as clickable hrefs to prevent javascript: XSS.
*/
function isSafeHref(url: string): boolean {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 [CRF-3] The web_fetch tool schema at toolDefinitions.ts uses z.string().url().describe("The URL to fetch (http or https)"). The description promises http/https but z.string().url() accepts javascript:alert(1), data:text/html,..., and any valid URL scheme. A .refine() or .regex() enforcing http/https at the schema level would protect every consumer, with isSafeHref remaining as defense-in-depth at the render layer. (Pariston)

🤖

Comment thread src/browser/features/Tools/WebFetchToolCall.tsx
Address coder-agents-review nit: the comment was too narrow (only
mentioned javascript:), but the function blocks all non-http(s) schemes.

https://claude.ai/code/session_015vY8c55jcKPoPNKm2264uC
@ThomasK33

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review coder-agents-review 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.

CRF-4 (Nit, comment wording) was addressed in 62ae660. The updated comment now lists the full scope of blocked schemes.

Three findings remain silent with no author response or code change:

  • CRF-2 (P2): Extract isSafeHref to shared browserUrl.ts utility and apply at WorkspaceStatusIndicator.tsx:77, which has the same unprotected href={agentStatus.url} from model-controlled data.
  • CRF-1 (P3): Add test coverage for the security boundary function (resolved automatically if CRF-2 extraction is adopted).
  • CRF-3 (P3): Add .refine() to the web_fetch tool schema to enforce http/https at the validation layer.

Further review is blocked until the author responds to these findings or pushes fixes. A reply acknowledging, contesting, or deferring each item (with a ticket for deferrals) unblocks the next round.

🤖 This review was automatically generated with Coder Agents.

@ThomasK33 ThomasK33 added this pull request to the merge queue May 28, 2026
Merged via the queue into main with commit 5e1e763 May 28, 2026
24 checks passed
@ThomasK33 ThomasK33 deleted the claude/autopatch-scan-18aa40f9-vuln-1275269-visAU branch May 28, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants