fix(sandbox): enforce path separator boundary in secureReadFile allowlist

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kyle
2026-07-09 12:44:21 -04:00
parent 54b9cbbf89
commit ebece69c56
2 changed files with 35 additions and 1 deletions

View File

@@ -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);
});
});

View File

@@ -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));