Enable `contextIsolation` in Electron to securely expose a limited set of Node.js APIs to the renderer process. It: 1. Isolates renderer and main process contexts. It ensures that the powerful main process functions aren't directly accessible from renderer process(es), adding a security boundary. 2. Mitigates remote exploitation risks. By isolating contexts, potential malicious code injections in the renderer can't directly reach and compromise the main process. 3. Reduces attack surface. 4. Protect against prototype pollution: It prevents tampering of JavaScript object prototypes in one context from affecting another context, improving app reliability and security. Supporting changes include: - Extract environment and system operations classes to the infrastructure layer. This removes node dependencies from core domain and application code. - Introduce `ISystemOperations` to encapsulate OS interactions. Use it from `CodeRunner` to isolate node API usage. - Add a preloader script to inject validated environment variables into renderer context. This keeps Electron integration details encapsulated. - Add new sanity check to fail fast on issues with preloader injected variables. - Improve test coverage of runtime sanity checks and environment components. Move validation logic into separate classes for Single Responsibility. - Improve absent value test case generation.
161 lines
5.3 KiB
TypeScript
161 lines
5.3 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { validateWindowVariables } from '@/infrastructure/Environment/WindowVariablesValidator';
|
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
|
|
import { WindowVariables } from '@/infrastructure/Environment/WindowVariables';
|
|
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
|
|
|
describe('WindowVariablesValidator', () => {
|
|
describe('validateWindowVariables', () => {
|
|
describe('invalid types', () => {
|
|
it('throws an error if variables is not an object', () => {
|
|
// arrange
|
|
const expectedError = 'window is not an object but string';
|
|
const variablesAsString = 'not an object';
|
|
// act
|
|
const act = () => validateWindowVariables(variablesAsString as unknown);
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
|
|
it('throws an error if variables is an array', () => {
|
|
// arrange
|
|
const expectedError = 'window is not an object but object';
|
|
const arrayVariables: unknown = [];
|
|
// act
|
|
const act = () => validateWindowVariables(arrayVariables as unknown);
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
|
|
describe('throws an error if variables is null', () => {
|
|
itEachAbsentObjectValue((absentValue) => {
|
|
// arrange
|
|
const expectedError = 'missing variables';
|
|
const variables = absentValue;
|
|
// act
|
|
const act = () => validateWindowVariables(variables as unknown);
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('property validations', () => {
|
|
it('throws an error with a description of all invalid properties', () => {
|
|
// arrange
|
|
const expectedError = 'Unexpected os (string)\nUnexpected isDesktop (string)';
|
|
const input = {
|
|
os: 'invalid',
|
|
isDesktop: 'not a boolean',
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input as unknown);
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
|
|
describe('`os` property', () => {
|
|
it('throws an error when os is not a number', () => {
|
|
// arrange
|
|
const expectedError = 'Unexpected os (string)';
|
|
const input = {
|
|
os: 'Linux',
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input as unknown);
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
|
|
it('throws an error for an invalid numeric os value', () => {
|
|
// arrange
|
|
const expectedError = 'Unexpected os (number)';
|
|
const input = {
|
|
os: Number.MAX_SAFE_INTEGER,
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input as unknown);
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
|
|
it('does not throw for a missing os value', () => {
|
|
const input = {
|
|
isDesktop: true,
|
|
system: new SystemOperationsStub(),
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input);
|
|
// assert
|
|
expect(act).to.not.throw();
|
|
});
|
|
});
|
|
|
|
describe('`isDesktop` property', () => {
|
|
it('throws an error when only isDesktop is provided and it is true without a system object', () => {
|
|
// arrange
|
|
const expectedError = 'Unexpected system (undefined)';
|
|
const input = {
|
|
isDesktop: true,
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input as unknown);
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
|
|
it('does not throw when isDesktop is true with a valid system object', () => {
|
|
// arrange
|
|
const input = {
|
|
isDesktop: true,
|
|
system: new SystemOperationsStub(),
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input);
|
|
// assert
|
|
expect(act).to.not.throw();
|
|
});
|
|
|
|
it('does not throw when isDesktop is false without a system object', () => {
|
|
// arrange
|
|
const input = {
|
|
isDesktop: false,
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input);
|
|
// assert
|
|
expect(act).to.not.throw();
|
|
});
|
|
});
|
|
|
|
describe('`system` property', () => {
|
|
it('throws an error if system is not an object', () => {
|
|
// arrange
|
|
const expectedError = 'Unexpected system (string)';
|
|
const input = {
|
|
isDesktop: true,
|
|
system: 'invalid system',
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input as unknown);
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('does not throw for a valid object', () => {
|
|
const input: WindowVariables = {
|
|
os: OperatingSystem.Windows,
|
|
isDesktop: true,
|
|
system: new SystemOperationsStub(),
|
|
};
|
|
// act
|
|
const act = () => validateWindowVariables(input);
|
|
// assert
|
|
expect(act).to.not.throw();
|
|
});
|
|
});
|
|
});
|