mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-05 21:50:46 +08:00
fix(export): add obsidian:// to allowed protocol whitelist (#14366)
<!-- Template from https://github.com/kubevirt/kubevirt/blob/main/.github/PULL_REQUEST_TEMPLATE.md?--> <!-- Thanks for sending a pull request! Here are some tips for you: 1. Consider creating this PR as draft: https://github.com/CherryHQ/cherry-studio/blob/main/CONTRIBUTING.md --> <!-- 🚨 Branch Strategy Change (Effective April 3, 2026) 🚨 The `main` branch is now under CODE FREEZE. - main branch: Only accepts critical bug fixes via `hotfix/*` branches. Fix PRs must be minimal in scope and must not include any refactoring code. - v2 branch: All new features, refactoring, and optimizations should be submitted to the `v2` branch. If you are submitting a bug fix to main, please ensure your PR is from a `hotfix/*` branch. --> ### What this PR does Before this PR: The "Export to Obsidian" feature reports "Export success" but no document is actually created in Obsidian. The `obsidian://new` URI is silently blocked by the protocol whitelist in `security.ts` and `preload/index.ts`, which only allows `http:`, `https:`, and `mailto:` schemes. After this PR: The `obsidian:` protocol is added to the allowed protocol whitelist in both `security.ts` and `preload/index.ts`, so the `obsidian://new` URI can be correctly forwarded to the system protocol handler and open Obsidian. <!-- (optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: --> Fixes # ### Why we need it and why it was done in this way The export flow works by: 1. Writing markdown content to the clipboard (`navigator.clipboard.writeText`) 2. Opening `obsidian://new?file=...&vault=...&clipboard` via `window.open()` 3. Electron's `setWindowOpenHandler` intercepts the call and checks `isSafeExternalUrl()` 4. `isSafeExternalUrl()` rejects `obsidian:` protocol → request is silently denied 5. Frontend has no failure detection and reports success The log confirms this with: `Blocked shell.openExternal for untrusted URL scheme: obsidian://new?file=...` The following tradeoffs were made: - Only `obsidian:` is added to the whitelist, keeping the security policy minimal. Unlike arbitrary custom protocols (e.g. `ms-msdt:`, `calculator:`), `obsidian:` is a well-known note-taking app protocol that Cherry Studio explicitly integrates with. The following alternatives were considered: - Writing files directly to the vault path via `fs.writeFile` in the main process, bypassing the URI scheme entirely. Rejected because it would be a larger change and the current URI-based approach is the intended design. Links to places where the discussion took place: <!-- optional: slack, other GH issue, mailinglist, ... --> ### Breaking changes None. <!-- optional --> ### Special notes for your reviewer - Reproduced on Windows with Cherry Studio v1.9.1. The bug is 100% reproducible on all platforms. - Verified fix with `pnpm dev` on Windows — Obsidian export works correctly after the change. - Unit test passes: `vitest run --project main src/main/services/__tests__/security.test.ts` (8 tests passed). <!-- optional --> ### Checklist This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR. Approvers are expected to review this list. - [x] PR: The PR description is expressive enough and will help future contributors - [x] Code: [Write code that humans can understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans) and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle) - [x] Refactor: You have [left the code cleaner than you found it (Boy Scout Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html) - [x] Upgrade: Impact of this change on upgrade flows was considered and addressed if required - [x] Documentation: A [user-guide update](https://docs.cherry-ai.com) was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior. - [x] Self-review: I have reviewed my own code (e.g., via [`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`, or GitHub UI) before requesting review from others ### Release note <!-- Write your release note: 1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required". 2. If no release note is required, just write "NONE". 3. Only include user-facing changes (new features, bug fixes visible to users, UI changes, behavior changes). For CI, maintenance, internal refactoring, build tooling, or other non-user-facing work, write "NONE". --> ```release-note fix: Obsidian export no longer silently fails — obsidian:// protocol is now allowed through the security whitelist ``` Signed-off-by: rfslzy <604364013@qq.com> Co-authored-by: SuYao <sy20010504@gmail.com>
This commit is contained in:
@@ -17,6 +17,10 @@ describe('isSafeExternalUrl', () => {
|
||||
expect(isSafeExternalUrl('mailto:user@example.com')).toBe(true)
|
||||
})
|
||||
|
||||
it('allows obsidian:// protocol', () => {
|
||||
expect(isSafeExternalUrl('obsidian://new?file=test&vault=myvault&clipboard')).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects file:// protocol', () => {
|
||||
expect(isSafeExternalUrl('file:///etc/passwd')).toBe(false)
|
||||
expect(isSafeExternalUrl('file://localhost/tmp')).toBe(false)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Security utility functions for the main process.
|
||||
*/
|
||||
|
||||
const ALLOWED_EXTERNAL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:'])
|
||||
const ALLOWED_EXTERNAL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'obsidian:'])
|
||||
|
||||
/**
|
||||
* Check whether a URL is safe to open via shell.openExternal().
|
||||
|
||||
@@ -434,7 +434,7 @@ const api = {
|
||||
shell: {
|
||||
openExternal: (url: string, options?: Electron.OpenExternalOptions) => {
|
||||
// Defense-in-depth: validate URL scheme before forwarding to shell.openExternal
|
||||
const ALLOWED_PROTOCOLS = ['http:', 'https:', 'mailto:']
|
||||
const ALLOWED_PROTOCOLS = ['http:', 'https:', 'mailto:', 'obsidian:']
|
||||
try {
|
||||
const parsed = new URL(url)
|
||||
if (!ALLOWED_PROTOCOLS.includes(parsed.protocol)) {
|
||||
|
||||
Reference in New Issue
Block a user