From ebece69c5650361b0add887980615e0e97da0fca Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 9 Jul 2026 12:44:21 -0400 Subject: [PATCH] fix(sandbox): enforce path separator boundary in secureReadFile allowlist Co-Authored-By: Claude Fable 5 --- .../main/__tests__/secure-read-file.test.ts | 25 +++++++++++++++++++ .../insomnia/src/main/secure-read-file.ts | 11 +++++++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 packages/insomnia/src/main/__tests__/secure-read-file.test.ts diff --git a/packages/insomnia/src/main/__tests__/secure-read-file.test.ts b/packages/insomnia/src/main/__tests__/secure-read-file.test.ts new file mode 100644 index 0000000000..b4fce61632 --- /dev/null +++ b/packages/insomnia/src/main/__tests__/secure-read-file.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('electron', () => ({ default: { app: { getPath: () => '/home/user/Insomnia' } } })); +vi.mock('insomnia-data', () => ({ services: { settings: { getOrCreate: vi.fn() } } })); + +import { isPathAllowed } from '../secure-read-file'; + +describe('isPathAllowed enforces a separator boundary on allowed roots', () => { + const root = '/opt/allowed-root'; + + it('allows the allowed root itself and files inside it', () => { + expect(isPathAllowed(root, [root]).isAllowed).toBe(true); + expect(isPathAllowed(`${root}/sub/file.txt`, [root]).isAllowed).toBe(true); + }); + + it('rejects a sibling directory that merely shares a name prefix', () => { + expect(isPathAllowed(`${root}-evil/secret`, [root]).isAllowed).toBe(false); + }); + + it('rejects a sibling install sharing a name prefix (e.g. Insomnia Nightly vs Insomnia)', () => { + const insomnia = '/apps/Insomnia'; + expect(isPathAllowed('/apps/Insomnia Nightly/insomnia.OAuth2Token.db', [insomnia]).isAllowed).toBe(false); + expect(isPathAllowed('/apps/Insomnia/insomnia.Request.db', [insomnia]).isAllowed).toBe(true); + }); +}); diff --git a/packages/insomnia/src/main/secure-read-file.ts b/packages/insomnia/src/main/secure-read-file.ts index 68c567d98e..532288d24c 100644 --- a/packages/insomnia/src/main/secure-read-file.ts +++ b/packages/insomnia/src/main/secure-read-file.ts @@ -12,7 +12,16 @@ import { SECURITY_SETTINGS_PATH_LABEL } from '../common/misc'; export const isPathAllowed = (filePath: string, userAllowList: string[]) => { const allowList = getSecuredFolderAllowList(userAllowList); const securedPath = securePath(filePath); - const isAllowed = allowList.some(f => path.resolve(f) !== '' && securedPath.startsWith(path.resolve(f))); + // Require an exact match or a separator-bounded prefix so a sibling dir sharing a name prefix + // (e.g. ".../Insomnia Nightly" for a ".../Insomnia" root) can't pass a bare `startsWith`. + const isAllowed = allowList.some(f => { + const root = path.resolve(f); + if (root === '') { + return false; + } + const rootWithSep = root.endsWith(path.sep) ? root : root + path.sep; + return securedPath === root || securedPath.startsWith(rootWithSep); + }); return { isAllowed, securedPath }; }; const securePath = (filePath: string) => path.resolve(decodeURIComponent(filePath));