Skip to content

fix(conversation-card): open VS Code URL with noopener and http(s) only - #1165

Open
juanmichelini wants to merge 8 commits into
mainfrom
openhands/fix-vscode-url-safe-open
Open

juanmichelini wants to merge 8 commits into
mainfrom
openhands/fix-vscode-url-safe-open

Conversation

@juanmichelini

@juanmichelini juanmichelini commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

HUMAN:

  • A human has tested these changes.

AGENT:


Why

The legacy OHE frontend opens the backend-provided VS Code URL with no protocol allow-list. A malicious value (javascript:, data:, file:) could run against the opener window, and an unparsable URL was passed through unchanged. The conversation card also opened it without noopener. This is the OSS→OHE parity gap tracked in #1149, mirroring OpenHands/OpenHands#19229. The Canvas bundle bump does not reach these files (P10: change the right frontend), so the fix goes into OHE's own copy.

Summary

  • frontend/src/utils/vscode-url-helper.ts: reject URLs that fail to parse or are not http:/https: (return null). The localhost rewrite still applies.
  • frontend/src/components/features/conversation-panel/conversation-card/conversation-card.tsx: open the transformed URL with "noopener,noreferrer".
  • frontend/src/hooks/query/use-unified-vscode-url.ts: when the helper rejects the sandbox's VSCODE URL, report VSCODE$URL_NOT_AVAILABLE. Without this the VS Code tab rendered the literal text null.
  • Tests: __tests__/utils/vscode-url-helper.test.ts (allow-list and rejected cases), __tests__/hooks/query/use-unified-vscode-url.test.tsx (rejected URLs → "not available"), __tests__/components/features/conversation-panel/conversation-card.test.tsx (card opens with noopener,noreferrer; never opens a javascript: URL).

Issue Number

Fixes #1149

How to Test

cd frontend
npx vitest run __tests__/utils/vscode-url-helper.test.ts \
  __tests__/hooks/query/use-unified-vscode-url.test.tsx \
  __tests__/components/features/conversation-panel/conversation-card.test.tsx \
  __tests__/routes/vscode-tab.test.tsx --no-coverage

All pass locally. eslint and prettier --check pass on the changed files, and npm run build succeeds.

How to verify on a preview

Where the change is visible. Only in the OHE legacy frontend, never in /canvas. /canvas is the separate OSS v1.35.0 bundle, which this PR does not change. / redirects to /canvas, so open the legacy routes below by direct URL.

The conversation card is not a UI surface. The sidebar conversation panel renders ConversationCard without showOptions, so its "Download via VS Code" menu item is never shown (conversation-card-actions.tsx: conversationId && showOptions ? onDownloadViaVSCode : undefined). Do not try to reach it. The unit tests cover the card.

The UI surface is the VS Code ("Code") tab of the legacy conversation page. It runs the same transformVSCodeUrl on the sandbox's VSCODE exposed URL (useUnifiedVSCodeUrl → GET /api/v1/sandboxes?id=<sandbox_id>).

Below, $APP is the preview origin, for example https://pr-NNNN.staging.all-hands.dev.

Step 0: bundle check (no sandbox needed)

M=$(curl -s "$APP/settings" | grep -o '/assets/manifest-[^"]*\.js' | head -1)
for c in $(curl -s "$APP$M" | grep -o '/assets/conversation-[A-Za-z0-9_-]*\.js' | sort -u); do
  curl -s "$APP$c" | grep -o 'new Set(\["http:","https:"\])\|"_blank","noopener,noreferrer"\|return [a-zA-Z_$]*?{url:[a-zA-Z_$]*,error:null}:{url:null,error:[a-zA-Z_$]*\.VSCODE\$URL_NOT_AVAILABLE}' | sort -u
done

Expected: one conversation-*.js chunk prints all three markers. The third marker is the new branch in useUnifiedVSCodeUrl. On main it is absent.

Step 1: get a conversation with a running sandbox

  1. Log in as the seeded member and start a conversation (Canvas is fine; it creates the same V1 conversation).
  2. Resolve the conversation id. If the URL shows task-<hex>, call GET $APP/api/v1/app-conversations/start-tasks?ids=<hex> and read app_conversation_id.
  3. Poll GET $APP/api/v1/app-conversations?ids=<conversation_id> until sandbox_status is RUNNING. Note sandbox_id.
  4. Optional: GET $APP/api/v1/sandboxes?id=<sandbox_id> should list an exposed_urls entry with name: "VSCODE" and an https:// URL.

Step 2: positive case (real URL)

  1. Open $APP/conversations/<conversation_id> (legacy UI, not /canvas/...). Wait for the agent to be ready.
  2. Click the Code tab ([data-testid="conversation-tab-vscode"]).
  3. Expected: an <iframe title="VS Code"> whose src is the https:// sandbox URL. If the protocols differ, an "Open in New Tab" button is shown instead. Either outcome passes. The text VS Code URL not available must not be shown.

Step 3: negative cases (injected URL)

For each value in javascript:alert(1), data:text/html,<script>alert(1)</script>, file:///etc/passwd, not-a-valid-url:

  1. Intercept the sandbox lookup and swap the VSCODE URL. Playwright example:
    await page.route('**/api/v1/sandboxes?id=*', async (route) => {
      const res = await route.fetch();
      const body = await res.json();
      for (const s of body) for (const u of (s?.exposed_urls ?? []))
        if (u.name === 'VSCODE') u.url = INJECTED;
      await route.fulfill({ response: res, json: body });
    });
    page.on('dialog', (d) => { throw new Error('dialog opened: ' + d.message()); });
  2. Open $APP/conversations/<conversation_id> in a fresh page (so the 5-minute sandbox query cache is empty), then click the Code tab.
  3. Expected: the tab shows exactly VS Code URL not available. There is no iframe[title="VS Code"], no "Open in New Tab" button, no new page or popup, and no alert dialog.
  4. Baseline for comparison (on main): for javascript: / data: / file: the tab shows the cross-origin warning with an "Open in New Tab" button that would window.open the injected URL. For not-a-valid-url it shows "Error parsing URL".

Verdict rule: PASS if Step 0 prints the three markers, Step 2 shows the editor (iframe or button), and every Step 3 value shows only "VS Code URL not available". If no sandbox reaches RUNNING, Step 0 plus the unit tests are the evidence. Report that sandbox start failed. It is not a defect in this PR.

Video/Screenshots

home (run 1)
after reload (run 1)

details

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

No dependency on the OSS pin bump; the OSS PR is not in a tagged release. The helper and card diffs mirror OSS, plus an inline // eslint-disable-next-line no-script-url on javascript: test literals (OHE's airbnb eslint config flags it). The useUnifiedVSCodeUrl change is OHE-only. OHE's VS Code tab would otherwise print null for a rejected URL.

This PR description was created by an AI agent (OpenHands) on behalf of @juanmichelini.


Enterprise server image for this PR:

ghcr.io/openhands/enterprise-server:sha-eaa28b1

The VS Code URL from the backend is opened in a new tab. Reject any URL
that is not http or https (or fails to parse) and open it with
"noopener,noreferrer" so a malicious value (javascript:, data:, file:)
cannot reach the opener window.

Mirrors OpenHands/OpenHands#19229 for the OHE legacy conversation card.

Co-authored-by: openhands <openhands@all-hands.dev>
@juanmichelini juanmichelini added the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026 — with OpenHands AI
@github-actions github-actions Bot added the type: fix A bug fix label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Coverage report

This PR does not seem to contain any modification to coverable code.

@juanmichelini

Copy link
Copy Markdown
Contributor Author

Preview verification finished: NEEDS CLARIFICATION. Evidence: https://github.com/OpenHands/saas-deploy/pull/1934#issuecomment-6622546155

This comment was posted by an AI agent (OpenHands) on behalf of the user.

@juanmichelini juanmichelini removed the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026
@juanmichelini juanmichelini added the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026 — with OpenHands AI
@juanmichelini juanmichelini removed the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026
…ndle)

Add .pr/evidence/bundle-proof.md showing the PR's fix is correctly compiled
into the OHE legacy frontend bundle (served at /), while the verifier
inspected the /canvas bundle built from OSS v1.35.0 (a separate SPA that the
issue excludes from scope). Includes grep proof from the built bundle.

Co-authored-by: openhands <openhands@all-hands.dev>
@juanmichelini juanmichelini added the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026 — with OpenHands AI
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

✅ PR Artifacts Cleaned Up

The .pr/ directory has been automatically removed.

@juanmichelini juanmichelini removed the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026
@juanmichelini juanmichelini added the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026 — with OpenHands AI
@juanmichelini

Copy link
Copy Markdown
Contributor Author

Preview verification failed. Details: https://github.com/OpenHands/saas-deploy/pull/1942#issuecomment-6630180970

This comment was posted by an AI agent (OpenHands) on behalf of the user.

@juanmichelini juanmichelini removed the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026
@juanmichelini

Copy link
Copy Markdown
Contributor Author

Preview verification finished: NEEDS CLARIFICATION. Evidence: https://github.com/OpenHands/saas-deploy/pull/1944#issuecomment-6630291968

This comment was posted by an AI agent (OpenHands) on behalf of the user.

@juanmichelini juanmichelini added the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 4, 2026 — with OpenHands AI
transformVSCodeUrl now returns null for non-http(s)/unparsable URLs, which
made the VS Code tab render the literal text "null". Report
VSCODE$URL_NOT_AVAILABLE instead. Add tests for the hook and for the
conversation card opening the URL with noopener,noreferrer.

Co-authored-by: openhands <openhands@all-hands.dev>
@openhands-agent openhands-agent added verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ and removed verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ labels Nov 6, 2026
@openhands-agent

Copy link
Copy Markdown
Contributor

The last preview verification (saas-deploy evidence) returned NEEDS CLARIFICATION because the preview went down mid-run. Changes in bbba635:

  • Fixed a defect: a VS Code URL rejected by the new allow-list made the legacy VS Code tab show the literal text null. useUnifiedVSCodeUrl now reports VS Code URL not available instead. I added a hook test; it fails without the fix.
  • Added card tests (mirroring OSS): the card opens the URL with noopener,noreferrer and never opens a javascript: URL.
  • Added a ## How to verify on a preview section to the PR description. The conversation card's VS Code item is never rendered in OHE, because the sidebar panel does not pass showOptions. The UI surface that can be tested is the Code tab on /conversations/:id. The section gives a bundle check, the positive steps, and the injected-URL negative steps, each with expected results.

I re-applied verify-this.

This comment was posted by an AI agent (OpenHands dev-coordinator) on behalf of the user.

@all-hands-bot

all-hands-bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Review posted for 4ae789bbdd59

Verdict: ✅ APPROVED. See the review.

This is an automated status.

@all-hands-bot all-hands-bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This review was posted by an AI agent (OpenHands).

The code changes look correct and implement the security fix properly:

  • ✅ transformVSCodeUrl now validates URLs and rejects non-http(s) protocols
  • ✅ window.open uses noopener,noreferrer
  • ✅ useUnifiedVSCodeUrl handles rejected URLs with proper error message
  • ✅ Tests comprehensively cover valid URLs, invalid URLs, and malicious payloads (javascript:, data:, file:)
  • ✅ PR follows the template with all required sections
  • ✅ Python tests, linting, and E2E tests pass

However, there is a blocking issue:

❌ FE Unit Tests (22) is failing

The FE Unit Tests check shows a failure. This must be resolved before the PR can be approved. Please review the test output to identify and fix the failing test.

🔄 CHANGES REQUESTED

LLM profile: sonnet-4-5 · Model: openhands/claude-sonnet-4-5-20250929

@juanmichelini

Copy link
Copy Markdown
Contributor Author

Preview verification finished: PASS. Evidence: https://github.com/OpenHands/saas-deploy/pull/2025#issuecomment-6674872628

This comment was posted by an AI agent (OpenHands) on behalf of the user.

@juanmichelini juanmichelini removed the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 6, 2026
Co-authored-by: openhands <openhands@all-hands.dev>
The signup-attribution test from #997 calls rememberCloudEntry under the
location stub from this file, which had only href. With origin undefined,
redirectUrl?.origin === window.location.origin was true for a null
redirectUrl and the code read null.searchParams. Browsers always set
location.origin, so stub it like a real Location.

Co-authored-by: openhands <openhands@all-hands.dev>
@juanmichelini juanmichelini removed the verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ label Nov 9, 2026

@all-hands-bot all-hands-bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This review was posted by an AI agent (OpenHands).

Previous review status: The commit 4b429633dde5 was approved after fixing a test failure.

Changes since previous review:

  • ✅ Only cleanup commit: The new head da6fbd37b contains a single automated commit that removes .pr/evidence/bundle-proof.md
  • ✅ Core security fix unchanged: All security changes remain intact (protocol validation, noopener,noreferrer, error handling)
  • ✅ PR marked ready: No longer in draft, verify-this label removed, indicating validation is complete
  • ✅ Live evidence provided: Screenshots added to PR description showing preview deployment verification

Summary:

The automated cleanup removed the .pr/ directory artifacts after the previous approval, as documented in AGENTS.md. The core security fix (protocol allow-list, noopener,noreferrer, rejected URL handling) remains unchanged and correctly implemented. All tests pass, and the PR has been validated on a preview deployment.

✅ APPROVED

LLM profile: unknown · Model: openhands/claude-sonnet-4-5-20250929

@openhands-agent openhands-agent added the blocked Automation could not finish this; needs a human (dev-coordinator) label Nov 9, 2026
@openhands-agent

Copy link
Copy Markdown
Contributor

Blocked, @juanmichelini: preview verification produced no verdict 2 times in a row (verifier or preview problem, not necessarily this PR). Remove blocked to let dev-coordinator pick it up again with a fresh round count.

This comment was posted by an AI agent (OpenHands dev-coordinator) on behalf of the user.

@juanmichelini juanmichelini added verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ and removed blocked Automation could not finish this; needs a human (dev-coordinator) labels Nov 10, 2026
@openhands-agent

Copy link
Copy Markdown
Contributor

Preview verification failed: no result 60 minutes after verify-this was added. dev-coordinator is restarting it.

This comment was posted by an AI agent (OpenHands dev-coordinator) on behalf of the user.

@openhands-agent openhands-agent added verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ and removed verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ labels Nov 10, 2026
@openhands-agent

Copy link
Copy Markdown
Contributor

Preview verification failed: no result 60 minutes after verify-this was added. dev-coordinator is restarting it.

This comment was posted by an AI agent (OpenHands dev-coordinator) on behalf of the user.

@openhands-agent openhands-agent added verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ blocked Automation could not finish this; needs a human (dev-coordinator) and removed verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ labels Nov 10, 2026
@openhands-agent

Copy link
Copy Markdown
Contributor

Blocked, @juanmichelini: preview verification produced no verdict 2 times in a row (verifier or preview problem, not necessarily this PR). Remove blocked to let dev-coordinator pick it up again with a fresh round count.

This comment was posted by an AI agent (OpenHands dev-coordinator) on behalf of the user.

@juanmichelini juanmichelini added verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ and removed verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ blocked Automation could not finish this; needs a human (dev-coordinator) labels Nov 10, 2026
@openhands-agent

Copy link
Copy Markdown
Contributor

Preview verification failed: no result 60 minutes after verify-this was added. dev-coordinator is restarting it.

This comment was posted by an AI agent (OpenHands dev-coordinator) on behalf of the user.

@openhands-agent openhands-agent added verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ and removed verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ labels Nov 10, 2026
@juanmichelini

Copy link
Copy Markdown
Contributor Author

Preview verification failed. Details: https://github.com/OpenHands/saas-deploy/pull/2157#issuecomment-6774465746

This comment was posted by an AI agent (OpenHands) on behalf of the user.

@juanmichelini juanmichelini added verify-this-on-vm Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ and removed verify-this Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ labels Nov 10, 2026
@openhands-agent openhands-agent added the blocked Automation could not finish this; needs a human (dev-coordinator) label Nov 11, 2026
@openhands-agent

Copy link
Copy Markdown
Contributor

Blocked, @juanmichelini: preview verification produced no verdict 2 times in a row (verifier or preview problem, not necessarily this PR). Remove blocked to let dev-coordinator pick it up again with a fresh round count.

This comment was posted by an AI agent (OpenHands dev-coordinator) on behalf of the user.

@juanmichelini

Copy link
Copy Markdown
Contributor Author

🔍 Preview Verification — PASS

Target: Replicated fleet VM shared-3 App: https://app.shared-3.replicated.all-hands-testing.dev
Validates: #1165 — open VS Code URL with noopener and http(s) only (baseline only; the change is not deployed to this VM)
Timestamp: 2026-11-11T02:34:57Z
Run ID: c5a1010b-f16c-4ceb-a3cc-85cc4cc88d24

Result: PASS

(Decided from the baseline alone, as instructed. The feature itself was not verified.)

Baseline smoke

Step Status Detail
preflight ✅ app -> 200
positive:login ✅ Keycloak login accepted
positive:home ✅ home-screen + chat-launcher visible
positive:backend ✅ /me 200, role=member
positive:reload ✅ login survived reload
positive:conversation ✅ conversation created
positive:reply ✅ final execution_status=finished (sandbox RUNNING); agent reply visible in the conversation. Conversation created 02:28:39Z, last updated 02:33:40Z (agent ran a multi-step task); within the 5 min budget, exact reply time not captured.
negative:rejected ✅ wrong-password login rejected by Keycloak

Feature test — VS Code URL protocol allow-list / noopener

Step Status Intended effect Observed effect (how verified) Screenshot
Conversation card opens VS Code URL with noopener,noreferrer; non-http(s) URL rejected ⏭️ window.open called with "noopener,noreferrer"; javascript:/data:/file: URLs never opened not tested: the change is not deployed to this VM unavailable

Key screenshots

home
conversation
reply

What was tested

  • Auth + UI health via the smoke script (login, home, /me role=member, reload persistence, conversation creation, wrong-password rejection).
  • Agent reply: /api/v1/app-conversations search showed execution_status=finished and the reply was visible in the conversation page.

Skipped / manual review

  • Feature check (VS Code URL opened with noopener,noreferrer; non-http(s) VS Code URL rejected): skipped, the change is not deployed to this VM.

Evidence

Full log: evidence/run-log.json


Generated by an AI agent (OpenHands).

@juanmichelini juanmichelini added verify-this-on-vm Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ and removed verify-this-on-vm Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/ labels Nov 11, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blocked Automation could not finish this; needs a human (dev-coordinator) type: fix A bug fix verify-this-on-vm Triggers Preview Verification on https://app.beta.staging.all-hands-testing.dev/canvas/automations/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[OSS→OHE parity] Open the VS Code URL safely from OHE's conversation card

3 participants