mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-10-23 03:31:56 +00:00
CVE-2020-10857: Whitelist safe URL protocols for shell.openExternal.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { app, dialog, shell } from 'electron';
|
||||
import { app, dialog } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { linuxUpdateNotification } from './linuxupdater'; // Required only in case of linux
|
||||
|
||||
import log from 'electron-log';
|
||||
import isDev from 'electron-is-dev';
|
||||
import * as ConfigUtil from '../renderer/js/utils/config-util';
|
||||
import * as LinkUtil from '../renderer/js/utils/link-util';
|
||||
|
||||
export function appUpdater(updateFromMenu = false): void {
|
||||
// Don't initiate auto-updates in development
|
||||
@@ -72,7 +73,7 @@ export function appUpdater(updateFromMenu = false): void {
|
||||
Current Version: ${app.getVersion()}`
|
||||
});
|
||||
if (response === 0) {
|
||||
shell.openExternal('https://zulipchat.com/apps/');
|
||||
LinkUtil.openBrowser(new URL('https://zulipchat.com/apps/'));
|
||||
}
|
||||
// Remove all autoUpdator listeners so that next time autoUpdator is manually called these
|
||||
// listeners don't trigger multiple times.
|
||||
|
@@ -7,6 +7,7 @@ import path from 'path';
|
||||
import * as DNDUtil from '../renderer/js/utils/dnd-util';
|
||||
import Logger from '../renderer/js/utils/logger-util';
|
||||
import * as ConfigUtil from '../renderer/js/utils/config-util';
|
||||
import * as LinkUtil from '../renderer/js/utils/link-util';
|
||||
import * as t from '../renderer/js/utils/translation-util';
|
||||
|
||||
const appName = app.name;
|
||||
@@ -48,7 +49,7 @@ function getToolsSubmenu(): Electron.MenuItemConstructorOptions[] {
|
||||
{
|
||||
label: t.__('Release Notes'),
|
||||
click() {
|
||||
shell.openExternal(`https://github.com/zulip/zulip-desktop/releases/tag/v${app.getVersion()}`);
|
||||
LinkUtil.openBrowser(new URL(`https://github.com/zulip/zulip-desktop/releases/tag/v${app.getVersion()}`));
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@@ -14,33 +14,19 @@
|
||||
<div class="maintenance-info">
|
||||
<p class="detail maintainer">
|
||||
Maintained by
|
||||
<a onclick="linkInBrowser('website')">Zulip</a>
|
||||
<a href="https://zulipchat.com" target="_blank" rel="noopener noreferrer">Zulip</a>
|
||||
</p>
|
||||
<p class="detail license">
|
||||
Available under the
|
||||
<a onclick="linkInBrowser('license')">Apache 2.0 License</a>
|
||||
<a href="https://github.com/zulip/zulip-desktop/blob/master/LICENSE" target="_blank" rel="noopener noreferrer">Apache 2.0 License</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
const { app } = require('electron').remote;
|
||||
const { shell } = require('electron');
|
||||
const version_tag = document.querySelector('#version');
|
||||
version_tag.innerHTML = 'v' + app.getVersion();
|
||||
|
||||
function linkInBrowser(type) {
|
||||
let url;
|
||||
switch (type) {
|
||||
case 'website':
|
||||
url = "https://zulipchat.com";
|
||||
break;
|
||||
case 'license':
|
||||
url = "https://github.com/zulip/zulip-desktop/blob/master/LICENSE";
|
||||
break;
|
||||
}
|
||||
shell.openExternal(url);
|
||||
}
|
||||
</script>
|
||||
<script>require('./js/shared/preventdrag.js')</script>
|
||||
</body>
|
||||
|
@@ -51,6 +51,6 @@ export default function handleExternalLink(this: WebView, event: Electron.NewWin
|
||||
ipcRenderer.removeAllListeners('downloadFileCompleted');
|
||||
});
|
||||
} else {
|
||||
shell.openExternal(url.href);
|
||||
LinkUtil.openBrowser(url);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ipcRenderer, remote, clipboard, shell } from 'electron';
|
||||
import { ipcRenderer, remote, clipboard } from 'electron';
|
||||
import { feedbackHolder } from './feedback';
|
||||
|
||||
import path from 'path';
|
||||
@@ -20,6 +20,7 @@ import Logger from './utils/logger-util';
|
||||
import * as CommonUtil from './utils/common-util';
|
||||
import * as EnterpriseUtil from './utils/enterprise-util';
|
||||
import * as AuthUtil from './utils/auth-util';
|
||||
import * as LinkUtil from './utils/link-util';
|
||||
import * as Messages from '../../resources/messages';
|
||||
|
||||
interface FunctionalTabProps {
|
||||
@@ -866,8 +867,7 @@ class ServerManagerView {
|
||||
|
||||
ipcRenderer.on('open-help', () => {
|
||||
// Open help page of current active server
|
||||
const helpPage = this.getCurrentActiveServer() + '/help';
|
||||
shell.openExternal(helpPage);
|
||||
LinkUtil.openBrowser(new URL('/help', this.getCurrentActiveServer()));
|
||||
});
|
||||
|
||||
ipcRenderer.on('reload-viewer', this.reloadView.bind(this, this.tabs[this.activeTabIndex].props.index));
|
||||
|
@@ -1,8 +1,7 @@
|
||||
'use-strict';
|
||||
|
||||
import { shell } from 'electron';
|
||||
|
||||
import BaseComponent from '../../components/base';
|
||||
import * as LinkUtil from '../../utils/link-util';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
export default class FindAccounts extends BaseComponent {
|
||||
@@ -45,7 +44,7 @@ export default class FindAccounts extends BaseComponent {
|
||||
if (!url.startsWith('http')) {
|
||||
url = 'https://' + url;
|
||||
}
|
||||
shell.openExternal(url + '/accounts/find');
|
||||
LinkUtil.openBrowser(new URL('/accounts/find', url));
|
||||
}
|
||||
|
||||
initListeners(): void {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { shell, ipcRenderer } from 'electron';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import BaseComponent from '../../components/base';
|
||||
import * as DomainUtil from '../../utils/domain-util';
|
||||
import * as LinkUtil from '../../utils/link-util';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
export default class NewServerForm extends BaseComponent {
|
||||
@@ -74,7 +75,7 @@ export default class NewServerForm extends BaseComponent {
|
||||
const link = 'https://zulipchat.com/new/';
|
||||
const externalCreateNewOrgElement = document.querySelector('#open-create-org-link');
|
||||
externalCreateNewOrgElement.addEventListener('click', () => {
|
||||
shell.openExternal(link);
|
||||
LinkUtil.openBrowser(new URL(link));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { shell } from 'electron';
|
||||
|
||||
import BaseSection from './base-section';
|
||||
import * as LinkUtil from '../../utils/link-util';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
export default class ShortcutsSection extends BaseSection {
|
||||
@@ -331,7 +330,7 @@ export default class ShortcutsSection extends BaseSection {
|
||||
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
|
||||
const externalCreateNewOrgElement = document.querySelector('#open-hotkeys-link');
|
||||
externalCreateNewOrgElement.addEventListener('click', () => {
|
||||
shell.openExternal(link);
|
||||
LinkUtil.openBrowser(new URL(link));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,14 +1,11 @@
|
||||
import { remote } from 'electron';
|
||||
|
||||
import cryptoRandomString from 'crypto-random-string';
|
||||
import * as ConfigUtil from './config-util';
|
||||
|
||||
const { shell } = remote;
|
||||
import * as LinkUtil from './link-util';
|
||||
|
||||
export const openInBrowser = (link: string): void => {
|
||||
const otp = cryptoRandomString({length: 64});
|
||||
ConfigUtil.setConfigItem('desktopOtp', otp);
|
||||
shell.openExternal(`${link}?desktop_flow_otp=${otp}`);
|
||||
LinkUtil.openBrowser(new URL(`?desktop_flow_otp=${encodeURIComponent(otp)}`, link));
|
||||
};
|
||||
|
||||
export const xorStrings = (a: string, b: string): string => {
|
||||
|
@@ -1,3 +1,45 @@
|
||||
import { shell } from 'electron';
|
||||
import escape from 'escape-html';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
export function isUploadsUrl(server: string, url: URL): boolean {
|
||||
return url.origin === server && url.pathname.startsWith('/user_uploads/');
|
||||
}
|
||||
|
||||
export function openBrowser(url: URL): void {
|
||||
if (['http:', 'https:', 'mailto:'].includes(url.protocol)) {
|
||||
shell.openExternal(url.href);
|
||||
} else {
|
||||
// For security, indirect links to non-whitelisted protocols
|
||||
// through a real web browser via a local HTML file.
|
||||
const dir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'zulip-redirect-')
|
||||
);
|
||||
const file = path.join(dir, 'redirect.html');
|
||||
fs.writeFileSync(file, `\
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Refresh" content="0; url=${escape(url.href)}" />
|
||||
<title>Redirecting</title>
|
||||
<style>
|
||||
html {
|
||||
font-family: menu, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>Opening <a href="${escape(url.href)}">${escape(url.href)}</a>…</p>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
shell.openItem(file);
|
||||
setTimeout(() => {
|
||||
fs.unlinkSync(file);
|
||||
fs.rmdirSync(dir);
|
||||
}, 15000);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user