Add deeplink for importing a site from a backup URL#3618
Draft
borkweb wants to merge 1 commit into
Draft
Conversation
## Summary Adds a new `wp-studio://import-backup?url=<url>` deeplink that downloads a backup file over https, validates it, and opens the Add Site modal pre-routed to the backup import step with the file ready to import. An optional `&name=<filename>` parameter overrides the filename derived from the URL. ## Why Studio already supports deeplinks for OAuth, sync-connect, and blueprint-based site creation, but there was no way for an external site (e.g. WordPress.com or a third-party host) to hand a user off into Studio with a specific backup file queued for import. Users had to download the backup manually, open Studio, start an Add Site flow, and re-select the file from disk. This closes that gap so a single click can take a user from a "Download backup" link into a populated Add Site → Import from a backup flow. Because the deeplink is triggerable from any web page, email, or IM, the inbound URL is fully attacker-controlled, and the downloaded backup carries PHP plugins/themes that Studio executes via WordPress Playground after import — so the URL has to be handled defensively. ## How The main-process handler (`apps/studio/src/lib/deeplink/handlers/import-backup.ts`) parses the URL directly — the value from `URLSearchParams.get()` is already percent-decoded, so it is deliberately not decoded a second time; re-decoding would let an attacker double-encode characters like `?` to shift the query boundary past a future allowlist check (the Apache CVE-2021-41773 shape). It rejects any scheme other than `https:` so a network MITM cannot swap the payload of a cleartext download, then downloads the backup to a sanitized path under the OS temp directory, validates the extension against `ACCEPTED_IMPORT_FILE_TYPES`, confirms the downloaded file is non-empty, and emits a new `import-backup-from-deeplink` IPC event with the path/name/size. On failure it cleans up the temp file and surfaces an error dialog with an "Open Studio Logs" shortcut; network errors get a connection-specific message. The renderer hook `useImportBackupDeeplink` listens for that event and feeds the file reference into the Add Site form, then flips an `isDeeplinkFlow` flag so the existing navigator routing logic in `add-site/index.tsx` jumps straight to `/backup/create`. Because the file is downloaded by the main process, it never passes through a browser `File` instance. To avoid a separate code path through `importFile`, `use-import-export.tsx` now accepts an `ImportSource = File | DeeplinkBackupFile` union and branches on `isDeeplinkBackupFile` to skip `getPathForFile` when the path is already known. For these deeplink imports it also passes `removeBackupOnComplete` so the main process deletes the downloaded temp file once the import settles, on both success and failure. `useAddSite` and the Add Site modal were updated to thread the wider type through. ## Testing - [ ] Trigger `wp-studio://import-backup?url=<https-url-to-a-valid-.zip-or-.tar.gz>` (e.g. via `open` on macOS) and confirm Studio opens, downloads the file, and the Add Site modal opens at the "Import from a backup" step with the backup name shown. - [ ] Trigger the same deeplink with `&name=my-export.tar.gz` and confirm the supplied name is used in the modal. - [ ] Trigger with an `http://` or `file://` URL and confirm the import is rejected with "Please check the link and try again." - [ ] Trigger with a URL whose path ends in `.txt` (or any extension outside `ACCEPTED_IMPORT_FILE_TYPES`) and confirm the error dialog appears with "Please check the link and try again." - [ ] Trigger with an unreachable host (e.g. `https://does-not-exist.invalid/site.zip`) and confirm the network-specific error message appears, and that "Open Studio Logs" opens the log file. - [ ] Trigger while Studio is minimized and confirm the window is restored and focused before the modal opens. - [ ] `npm test -- apps/studio/src/lib/deeplink/tests/import-backup.test.ts` passes. - [ ] `npx eslint --fix` on modified files and `npm run typecheck` are clean.
e37c09a to
230cc95
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a
wp-studio://import-backup?url=<url>deeplink that downloads a backup over https, validates it, and opens the Add Site modal at the import step with the file ready to go. Optional&name=<filename>overrides the filename derived from the URL.Why
There was no way for an external site (WordPress.com, a third-party host) to hand a user into Studio with a backup queued for import — they had to download it, open Studio, and re-select the file by hand. The deeplink is triggerable from any web page, email, or IM, so the URL is attacker-controlled, and backups run PHP via Playground on import — hence the defensive URL handling below.
How
handlers/import-backup.ts): parses the URL without re-decoding (URLSearchParams.get()already decodes it; a second decode would allow double-encoding past a future allowlist — the CVE-2021-41773 shape), rejects non-https:schemes (blocks MITM payload swaps), downloads to a sanitized temp path, validates extension and non-empty size, then emitsimport-backup-from-deeplink. Failures clean up the temp file and show an error dialog.useImportBackupDeeplinkfeeds the file into the Add Site form and routes to/backup/create. Since the file never passes through a browserFile,use-import-export.tsxtakes anImportSource = File | DeeplinkBackupFileunion and passesremoveBackupOnCompleteso the temp download is deleted after import.Testing
.zip/.tar.gzURL → Studio opens at the backup import step with the name shown.&name=my-export.tar.gz→ supplied name is used.http://orfile://URL → rejected with "Please check the link and try again.".txt) → same error dialog.npm test -- apps/studio/src/lib/deeplink/tests/import-backup.test.tspasses.Related issues