Repository: gitify-app/gitify (ts)

Issue:
Link Sanitization, Encrypting Cookies, and Electron.js Version
### 📝 Provide a description of the new feature

***Summary:***
Thank you for designing the Gitify Desktop Application making it open-source and available. The application does a great job of gathering and managing github notifications. We list pointers of concern below that can help make the application more secure.

1. [Sanitizing Links] While the application uses `shell.openExternal()` when passing links to the user’s system, it will be helpful to ensure that such links do not use the `file:///` protocol to avoid the execution of sensitive files on the user’s system. For example, passing `shell.openExternal(file:///Applications/Emacs.app/Contents/MacOS/Emacs)` will open the Emacs app on the user’s device if present.
2. [Encrypting Cookies]: Since the application syncs with external accounts, it will be helpful to store information in cookies and then use `@electron/fuses` to encrypt cookies on the filesystem. [[Link](https://www.electronjs.org/docs/latest/tutorial/fuses#cookieencryption)]
3. [Keeping up-to-date w/ Electron.js]: The application uses an old version of Electron.js and Chromium which is vulnerable to numerous known V8 and Blink attacks. We observed an existing issue for an upgrade to v22, which will be great for the app [[Issue](https://github.com/gitify-app/gitify/issues/576)]. Upgrading to an even newer version will be a great idea as well [[Link](https://github.com/snaildos/Fifo-Browser/pull/281)]

Thank you!

***Platform(s) Affected:***
Windows, Linux, MacOS

–
Mir Masood Ali, PhD student, University of Illinois at Chicago
Mohammad Ghasemisharif, PhD Candidate, University of Illinois at Chicago
Chris Kanich, Associate Professor, University of Illinois at Chicago
Jason Polakis, Associate Professor, University of Illinois at Chicago

### ➕ Additional Information

_No response_

Pull request:
feat: prevent local files from opening
Closes #681 part [1]

Patch:
diff --git a/src/components/Repository.tsx b/src/components/Repository.tsx
index 27e72085..3cdb5820 100644
--- a/src/components/Repository.tsx
+++ b/src/components/Repository.tsx
@@ -1,5 +1,3 @@
-const { shell } = require('electron');
-
 import React, { useCallback, useContext } from 'react';
 import { ReadIcon } from '@primer/octicons-react';
 import { CSSTransition, TransitionGroup } from 'react-transition-group';
@@ -7,6 +5,7 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group';
 import { AppContext } from '../context/App';
 import { Notification } from '../typesGithub';
 import { NotificationRow } from './NotificationRow';
+import { openExternalLink } from '../utils/comms';
 
 interface IProps {
   hostname: string;
@@ -23,7 +22,7 @@ export const RepositoryNotifications: React.FC<IProps> = ({
 
   const openBrowser = useCallback(() => {
     const url = repoNotifications[0].repository.html_url;
-    shell.openExternal(url);
+    openExternalLink(url);
   }, [repoNotifications]);
 
   const markRepoAsRead = useCallback(() => {
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 87b67326..38574a81 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -1,5 +1,5 @@
 import { BellIcon } from '@primer/octicons-react';
-import { ipcRenderer, shell } from 'electron';
+import { ipcRenderer } from 'electron';
 import React, { useCallback, useContext, useMemo } from 'react';
 import { useNavigate, useLocation } from 'react-router-dom';
 
@@ -9,6 +9,7 @@ import { IconCog } from '../icons/Cog';
 import { IconQuit } from '../icons/Quit';
 import { IconRefresh } from '../icons/Refresh';
 import { Constants } from '../utils/constants';
+import { openExternalLink } from '../utils/comms';
 
 export const Sidebar: React.FC = () => {
   const navigate = useNavigate();
@@ -18,11 +19,11 @@ export const Sidebar: React.FC = () => {
   const { notifications, fetchNotifications } = useContext(AppContext);
 
   const onOpenBrowser = useCallback(() => {
-    shell.openExternal(`https://github.com/${Constants.REPO_SLUG}`);
+    openExternalLink(`https://github.com/${Constants.REPO_SLUG}`);
   }, []);
 
   const onOpenGitHubNotifications = useCallback(() => {
-    shell.openExternal(`https://github.com/notifications`);
+    openExternalLink(`https://github.com/notifications`);
   }, []);
 
   const quitApp = useCallback(() => {
diff --git a/src/routes/LoginWithToken.tsx b/src/routes/LoginWithToken.tsx
index 32b3660f..d14285bb 100644
--- a/src/routes/LoginWithToken.tsx
+++ b/src/routes/LoginWithToken.tsx
@@ -2,12 +2,12 @@ import React, { useCallback, useContext, useState } from 'react';
 import { Form, FormRenderProps } from 'react-final-form';
 import { ArrowLeftIcon } from '@primer/octicons-react';
 import { useNavigate } from 'react-router-dom';
-import { shell } from 'electron';
 
 import { AppContext } from '../context/App';
 import { AuthTokenOptions } from '../types';
 import { Constants } from '../utils/constants';
 import { FieldInput } from '../components/fields/FieldInput';
+import { openExternalLink } from '../utils/comms';
 
 interface IValues {
   token?: string;
@@ -46,7 +46,7 @@ export const LoginWithToken: React.FC = () => {
   const [isValidToken, setIsValidToken] = useState<boolean>(true);
 
   const openLink = useCallback((url: string) => {
-    shell.openExternal(url);
+    openExternalLink(url);
   }, []);
 
   const renderForm = (formProps: FormRenderProps) => {
diff --git a/src/utils/comms.ts b/src/utils/comms.ts
index 09fe3a30..03beca86 100644
--- a/src/utils/comms.ts
+++ b/src/utils/comms.ts
@@ -1,7 +1,9 @@
 import { ipcRenderer, shell } from 'electron';
 
 export function openExternalLink(url: string): void {
-  shell.openExternal(url);
+  if (!url.toLowerCase().startsWith('file:///')) {
+    shell.openExternal(url);
+  }
 }
 
 export function setAutoLaunch(value: boolean): void {


Test patch:
diff --git a/src/utils/comms.test.ts b/src/utils/comms.test.ts
index 166f5d93..4a9edb03 100644
--- a/src/utils/comms.test.ts
+++ b/src/utils/comms.test.ts
@@ -41,6 +41,11 @@ describe('utils/comms.ts', () => {
     expect(shell.openExternal).toHaveBeenCalledWith('http://www.gitify.io/');
   });
 
+  it('should ignore opening external local links file:///', () => {
+    openExternalLink('file:///Applications/SomeApp.app');
+    expect(shell.openExternal).toHaveBeenCalledTimes(0);
+  });
+
   it('should setAutoLaunch (true)', () => {
     setAutoLaunch(true);