fix(vscode): wire native file dialog for embedded-app Browse button#1235
fix(vscode): wire native file dialog for embedded-app Browse button#1235meetp06 wants to merge 1 commit into
Conversation
…ocketride-org#1011) The Dropper "Browse" button posts {type:'requestFileDialog'}, which the openLink webview bridge forwards to the extension host. But the host's onDidReceiveMessage only implemented clipboard paste/copy — it never handled requestFileDialog. So the request arrived, no native dialog opened, and nativeFilesSelected was never posted back: the button did nothing, while clicking elsewhere in the drop zone (the in-iframe <input type=file>) still worked. That matches the report exactly. Add the missing requestFileDialog case: open vscode.window.showOpenDialog, read the selected files, and post them back as nativeFilesSelected (buffers as number[] so they survive webview message serialization). Mirrors the existing clipboard and drag-drop bridges in the same handler. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughProjectProvider's webview-to-host message bridge now handles a new ChangesFile Dialog Webview-to-Host Handler
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 Internal: Discord sync markerAuto-managed by the Discord notification workflow. Stores the linked Discord message ID. Do not edit or delete. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/vscode/src/providers/ProjectProvider.ts`:
- Around line 861-882: The host currently honors msg.type ===
'requestFileDialog' from any iframe and returns local file bytes via
panel.webview.postMessage, allowing arbitrary embedded origins (via openLink())
to trigger file selection and exfiltrate files; update ProjectProvider's message
handler to validate the message origin against a whitelist of trusted iframe
origins before handling 'requestFileDialog' (and similarly guard forwarding code
in the inline bridge script), e.g., check the source/origin of the incoming
webview message or an allowlist stored on the ProjectProvider instance and only
call vscode.window.showOpenDialog and
panel.webview.postMessage({type:'nativeFilesSelected',...}) when the origin
matches a trusted entry; follow repository ESLint/Prettier conventions when
adding the guard and reuse existing symbols like openLink(), panel.webview, and
the requestFileDialog/nativeFilesSelected message types.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4652d0ea-243f-4b6f-a21a-5b3328fce4bd
📒 Files selected for processing (1)
apps/vscode/src/providers/ProjectProvider.ts
| } else if (msg?.type === 'requestFileDialog') { | ||
| // The embedded app's "Browse" button can't open an OS file picker from | ||
| // inside the sandboxed iframe, so it posts {type:'requestFileDialog'} up to | ||
| // the bridge, which forwards it here. Open the native dialog on the host, | ||
| // read the chosen files, and post them back as {type:'nativeFilesSelected'}; | ||
| // the bridge relays that into the iframe. Buffers go as number[] so they | ||
| // survive webview message serialization. | ||
| try { | ||
| const uris = await vscode.window.showOpenDialog({ canSelectMany: true, openLabel: 'Select' }); | ||
| if (!uris || uris.length === 0) return; | ||
| const files = await Promise.all( | ||
| uris.map(async (uri) => ({ | ||
| name: uri.path.split('/').pop() || 'file', | ||
| type: '', | ||
| lastModified: Date.now(), | ||
| buffer: Array.from(await vscode.workspace.fs.readFile(uri)), | ||
| })) | ||
| ); | ||
| panel.webview.postMessage({ type: 'nativeFilesSelected', files }); | ||
| } catch (error) { | ||
| this.logger.error(`[ProjectProvider] Native file dialog failed: ${error}`); | ||
| } |
There was a problem hiding this comment.
Gate native file-dialog bridging to trusted iframe origins.
At Line 861, the host accepts requestFileDialog solely by message type. In this flow, openLink() can embed arbitrary URLs, so any embedded origin can request local file selection and receive file bytes back through nativeFilesSelected. This expands local-file exfiltration capability beyond the Dropper origin.
Suggested hardening
@@
- panel.webview.onDidReceiveMessage(async (msg) => {
+ const trustedOrigin = (() => {
+ try {
+ return new URL(url).origin;
+ } catch {
+ return '';
+ }
+ })();
+ panel.webview.onDidReceiveMessage(async (msg) => {
@@
- } else if (msg?.type === 'requestFileDialog') {
+ } else if (msg?.type === 'requestFileDialog') {
+ if (msg?.origin !== trustedOrigin) {
+ this.logger.error(`[ProjectProvider] Blocked requestFileDialog from untrusted origin: ${String(msg?.origin)}`);
+ return;
+ }
@@And in the inline bridge script where iframe messages are forwarded:
- if (event.data.type === 'requestFileDialog') vscode.postMessage({ type: 'requestFileDialog' });
+ if (event.data.type === 'requestFileDialog') vscode.postMessage({ type: 'requestFileDialog', origin: event.origin });As per coding guidelines, apply repository ESLint/Prettier conventions when introducing this guard logic.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/vscode/src/providers/ProjectProvider.ts` around lines 861 - 882, The
host currently honors msg.type === 'requestFileDialog' from any iframe and
returns local file bytes via panel.webview.postMessage, allowing arbitrary
embedded origins (via openLink()) to trigger file selection and exfiltrate
files; update ProjectProvider's message handler to validate the message origin
against a whitelist of trusted iframe origins before handling
'requestFileDialog' (and similarly guard forwarding code in the inline bridge
script), e.g., check the source/origin of the incoming webview message or an
allowlist stored on the ProjectProvider instance and only call
vscode.window.showOpenDialog and
panel.webview.postMessage({type:'nativeFilesSelected',...}) when the origin
matches a trusted entry; follow repository ESLint/Prettier conventions when
adding the guard and reuse existing symbols like openLink(), panel.webview, and
the requestFileDialog/nativeFilesSelected message types.
Source: Coding guidelines
Summary
In the Dropper node UI, the Browse button inside the drop area did nothing — clicking anywhere else in the drop zone opened the picker, but the button itself was dead (#1011).
Root cause
The "Browse" button can't open an OS file picker from inside the sandboxed webview iframe, so it uses a host round-trip:
DropZoneBrowse button →window.parent.postMessage({type:'requestFileDialog'})(apps/dropper-ui/.../DropperContainer.tsx)openLinkwebview bridge forwards it to the extension host:if (event.data.type === 'requestFileDialog') vscode.postMessage({type:'requestFileDialog'})(ProjectProvider.ts:837)ProjectProvider.ts:842)DropperContainerlistens fornativeFilesSelectedand adds the filesThe host-side handler was the missing link.
panel.webview.onDidReceiveMessageinopenLink()only implementedrequestPaste/copyText— there was norequestFileDialogcase, andvscode.window.showOpenDialogis never called anywhere in the extension. So the forwarded request was received and silently dropped: no dialog, nonativeFilesSelected. Drag-and-drop and the drop-zone click kept working because those don't need the host (the drop bridge reads files in-browser; the click uses the in-iframe<input type=file>).Fix
Add the missing
requestFileDialogcase to the host handler — open the native dialog, read the selected files, and post them back asnativeFilesSelected. It mirrors the existing clipboard and drag-drop bridges in the same place.bufferis sent asnumber[]to survive webview message serialization; the shape ({name, type, lastModified, buffer}) matches exactly what thenativeFilesSelectedlistener consumes (new Uint8Array(f.buffer)).Verification
ProjectProvider. A maintainer clicking Browse in the Dropper is the final check; CI covers TypeScript compilation.Closes #1011
🤖 Generated with Claude Code
Summary by CodeRabbit