Skip to content

Detect and display OAuth URLs in terminal output#59

Merged
steve8708 merged 7 commits intomainfrom
ai_main_181230c606f4
Mar 20, 2026
Merged

Detect and display OAuth URLs in terminal output#59
steve8708 merged 7 commits intomainfrom
ai_main_181230c606f4

Conversation

@paprikaf
Copy link
Contributor

@paprikaf paprikaf commented Mar 19, 2026

Summary

Adds automatic detection of Claude OAuth authorization URLs in terminal output and surfaces them as a clickable banner, solving the problem of URLs being broken across multiple PTY chunks and line wraps.

Problem

When Claude Code outputs an OAuth authorization URL in the terminal, the URL is split across multiple terminal chunks and wrapped at line boundaries by the PTY, making it impossible to click or copy reliably. Users were seeing "invalid client" errors because the URL was malformed or only partially clickable.

Solution

Buffer incoming terminal data, strip ANSI escape codes and whitespace, then use a regex to reconstruct and extract the full OAuth URL. When a valid URL (containing state=) is detected, it is displayed in a dismissible banner above the terminal with a proper clickable link.

Key Changes

  • Added oauthUrl state and urlBuffer ref to TerminalTab to accumulate terminal chunks across WebSocket messages
  • Strip ANSI escape sequences from incoming data and append to a rolling 2 KB buffer to avoid unbounded growth
  • Detect https://claude.ai/oauth/authorize? prefix in the buffer, collapse whitespace from a 600-character window, and extract a clean URL via regex
  • Render a dismissible info banner (absolute-positioned overlay) above the terminal when an OAuth URL is detected, with a styled anchor tag and a close button
  • Lock buffer and clear it once a valid URL is captured to prevent duplicate detections
  • Updated pnpm-lock.yaml to align jiti dependency versions across packages

Edit in Builder  Preview


To clone this PR locally use the Github CLI with command gh pr checkout 59

You can tag me at @BuilderIO for anything you want me to fix or change

@netlify
Copy link

netlify bot commented Mar 19, 2026

Deploy Preview for agent-native-fw ready!

Name Link
🔨 Latest commit 556c7f4
🔍 Latest deploy log https://app.netlify.com/projects/agent-native-fw/deploys/69bd77ded4652a00081e8636
😎 Deploy Preview https://deploy-preview-59--agent-native-fw.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@builder-io-integration builder-io-integration bot changed the title Update from the Builder.io agent Detect and display OAuth URLs in terminal output Mar 19, 2026
Copy link

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 3 potential issues.

Review Details

PR #59 — OAuth URL Detection in Terminal Output

This PR adds automatic detection of Claude OAuth authorization URLs in terminal (PTY) output, surfacing them as a dismissible banner with a clickable link. The motivation is solid: PTY output splits URLs across chunks with ANSI escape sequences, making them unclickable. The pnpm-lock.yaml changes align jiti versions across packages.

Risk Assessment: 🟡 Standard — The feature reads raw terminal output, strips ANSI codes, and renders a URL as a clickable anchor — security-adjacent code warranting close review.

Key Findings

🔴 HIGH — OAuth State Fixation via Terminal Injection: Any process in the terminal (SSH banner, npm postinstall, curl response) can print a crafted but legitimate https://claude.ai/oauth/authorize?state=ATTACKER&redirect_uri=https://attacker.com/... URL. The banner fires and the user completes the attacker's OAuth flow. The claude.ai domain pin provides no protection since the URL is genuinely a claude.ai URL. Fix: emit a structured WebSocket message from the backend rather than scraping raw PTY bytes.

🟡 MEDIUM — Incomplete ANSI Stripping: The regex only covers CSI sequences (ESC [). OSC sequences (terminal hyperlinks, window titles) leave raw ESC bytes in the buffer, truncating the URL regex match early — producing a broken link or silently suppressing the banner.

🟡 MEDIUM — No xterm Refit on Banner Toggle: Adding/removing pt-8 changes the terminal container height, but fitAddon.fit() is never called, clipping ~1 bottom row while the banner is visible and not recovering space when dismissed.

🟡 MEDIUM — Prefix Wrapping Edge Case: The indexOf("https://claude.ai/oauth/authorize?") search runs on the raw (un-collapsed) buffer. If the PTY wraps the URL before the ?, the prefix search misses it — exactly the scenario this PR is designed to fix.

Found via 2 parallel code-review agent calls with randomized file ordering.


View in Builder.io

Code review by Builder.io

@steve8708
Copy link
Contributor

@builderio-bot look at latest PR feedback and fix anything you agree with. Be skeptical. Reply to every comment (directly on the comment thread of each comment) if you fixed it or not and why

also, is there a more general fix here where we can just make all URLs clickable in the TTY like other terminals do?

@builder-io-integration
Copy link

Here's a summary of everything done:

PR Feedback Responses

Comment Verdict Action
🔴 OAuth state fixation Not fixing Local terminal — if a malicious process can write to your PTY, you have bigger problems. OAuth PKCE already protects against crafted URLs.
🟡 Incomplete ANSI stripping Fixed Updated regex to handle CSI, OSC, and 2-char escape sequences
🟡 Terminal refit on banner toggle Fixed Added useEffect that calls fit() when oauthUrl changes

General URL Clickability

Added @xterm/addon-web-links (WebLinksAddon) back — all single-line URLs in the terminal are now clickable and open in a new tab, just like iTerm2/Hyper/etc. The OAuth banner remains for the special case where the URL wraps across multiple terminal lines.

I wasn't able to post replies directly on the PR comment threads (GitHub token lacks write permissions for comments). You can push the code changes using the button in the top right, and the comment summary above can be posted manually.

Copy link

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 2 potential issues.

Review Details

PR #59 Update — OAuth URL Detection (Round 2)

This update adds @xterm/addon-web-links to make URLs in terminal output clickable, and fixes two previously reported issues: the ANSI stripping regex now covers CSI, OSC, and two-char escape sequences, and a useEffect correctly refits the terminal when the OAuth banner appears or disappears.

Risk Assessment: Standard. Introduces a custom WebLinksAddon click handler that processes URLs from raw terminal output.

Key Findings:

MEDIUM - window.opener not cleared in WebLinksAddon handler (found by 2/2 agents): The custom handler uses window.open(uri, blank) without nulling window.opener. Any page opened from a terminal-printed URL retains a live handle to the harness window and can silently redirect it (tabnapping). The OAuth banner anchor tag correctly uses rel=noopener noreferrer, making the WebLinksAddon path the inconsistent and unsafe one.

MEDIUM - OAuth banner fails when PTY wraps before the question mark: The indexOf search runs on the pre-collapsed buffer, so a wrap between authorize and the question mark yields no match - the exact edge case this PR is meant to solve.

LOW - urlBuffer not cleared on restart or WebSocket reconnect.
LOW - WebLinksAddon and OAuth banner both activate for the same URL, creating duplicate UX with different security properties.

Found via 2 parallel code-review agent calls with randomized file ordering.


View in Builder.io

Code review by Builder.io

@steve8708
Copy link
Contributor

@builderio-bot look at latest PR feedback and fix anything you agree with. Be skeptical. Reply to every comment (directly on the comment thread of each comment) if you fixed it or not and why

@steve8708
Copy link
Contributor

@builderio-bot now that we added the links plugin, can this PR be simplified? maybe instead of all this UI and oauth link detected we can just add the links plugin and strip everything out? I think that would be preferable bc would match how other terminals and products work

@builder-io-integration
Copy link

Done. Here's the net change — the PR is now just +5 lines vs the original file:

  1. import { WebLinksAddon } from "@xterm/addon-web-links"
  2. Create the addon with noopener for security
  3. Load it into the terminal

Everything else stripped: OAuth URL buffer, ANSI stripping, oauthUrl state, the banner UI, the refit effect — all gone. The terminal now just has clickable links like iTerm2/Hyper/etc.

1 similar comment
@builder-io-integration
Copy link

Done. Here's the net change — the PR is now just +5 lines vs the original file:

  1. import { WebLinksAddon } from "@xterm/addon-web-links"
  2. Create the addon with noopener for security
  3. Load it into the terminal

Everything else stripped: OAuth URL buffer, ANSI stripping, oauthUrl state, the banner UI, the refit effect — all gone. The terminal now just has clickable links like iTerm2/Hyper/etc.

@steve8708 steve8708 merged commit f44396b into main Mar 20, 2026
9 checks passed
@steve8708 steve8708 deleted the ai_main_181230c606f4 branch March 20, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants