1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-02 12:47:41 +02:00

Desktop: Security: Prevent calling arbitrary commands via x-callback-url

This commit is contained in:
Laurent Cozic 2023-06-14 15:51:35 +01:00
parent 0f9727144f
commit 69826610a2
3 changed files with 7 additions and 3 deletions

View File

@ -34,7 +34,7 @@ import ShareFolderDialog from '../ShareFolderDialog/ShareFolderDialog';
import { ShareInvitation } from '@joplin/lib/services/share/reducer'; import { ShareInvitation } from '@joplin/lib/services/share/reducer';
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems'; import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils'; import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { parseCallbackUrl } from '@joplin/lib/callbackUrlUtils'; import { isCallbackUrl, parseCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import ElectronAppWrapper from '../../ElectronAppWrapper'; import ElectronAppWrapper from '../../ElectronAppWrapper';
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils'; import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types'; import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
@ -173,6 +173,7 @@ class MainScreenComponent extends React.Component<Props, State> {
} }
private openCallbackUrl(url: string) { private openCallbackUrl(url: string) {
if (!isCallbackUrl(url)) throw new Error(`Invalid callback URL: ${url}`);
const { command, params } = parseCallbackUrl(url); const { command, params } = parseCallbackUrl(url);
void CommandService.instance().execute(command.toString(), params.id); void CommandService.instance().execute(command.toString(), params.id);
} }

View File

@ -3,13 +3,14 @@ import * as callbackUrlUtils from './callbackUrlUtils';
describe('callbackUrlUtils', () => { describe('callbackUrlUtils', () => {
it('should identify valid callback urls', () => { it('should identify valid callback urls', () => {
const url = 'joplin://x-callback-url/123?a=b'; const url = 'joplin://x-callback-url/openFolder?a=b';
expect(callbackUrlUtils.isCallbackUrl(url)).toBe(true); expect(callbackUrlUtils.isCallbackUrl(url)).toBe(true);
}); });
it('should identify invalid callback urls', () => { it('should identify invalid callback urls', () => {
expect(callbackUrlUtils.isCallbackUrl('not-joplin://x-callback-url/123?a=b')).toBe(false); expect(callbackUrlUtils.isCallbackUrl('not-joplin://x-callback-url/123?a=b')).toBe(false);
expect(callbackUrlUtils.isCallbackUrl('joplin://xcallbackurl/123?a=b')).toBe(false); expect(callbackUrlUtils.isCallbackUrl('joplin://xcallbackurl/123?a=b')).toBe(false);
expect(callbackUrlUtils.isCallbackUrl('joplin://x-callback-url/invalidCommand?a=b')).toBe(false);
}); });
it('should build valid note callback urls', () => { it('should build valid note callback urls', () => {

View File

@ -1,7 +1,9 @@
const URL = require('url-parse'); const URL = require('url-parse');
export function isCallbackUrl(s: string) { export function isCallbackUrl(s: string) {
return s.startsWith('joplin://x-callback-url/'); return s.startsWith('joplin://x-callback-url/openNote?') ||
s.startsWith('joplin://x-callback-url/openFolder?') ||
s.startsWith('joplin://x-callback-url/openTag?');
} }
export function getNoteCallbackUrl(noteId: string) { export function getNoteCallbackUrl(noteId: string) {