Skip to content

Commit 9bf7819

Browse files
committed
feat: add comprehensive e2e security test suite
- Add Playwright-based security testing framework - Implement context isolation validation tests - Add sandbox security validation tests - Include RCE exposure detection tests - Add authentication flow regression tests - Set up GitHub Actions workflow for automated security testing - Update gitignore to exclude test artifacts and reports This establishes automated security testing to validate context isolation and sandbox configurations, helping prevent RCE vulnerabilities. fix: add TypeScript configuration for e2e security tests - Create tsconfig.e2e-security.json for e2e security test files - Update ESLint parser options to include e2e security TypeScript config - Resolves ESLint parsing errors for test/e2e-security/**/* files This fixes the CI lint failures by ensuring ESLint can properly parse the new e2e security test TypeScript files. fix: add ESLint overrides for e2e security tests - Disable strict linting rules for test/e2e-security/**/*.ts files - Allow console statements, missing JSDoc, unused vars in test files - Disable no-unsanitized/property for security test injection scenarios This allows security test files to use console logging and test-specific patterns while maintaining strict linting for production code. fix: auto-format e2e security test files - Apply prettier formatting to all e2e security test files - Fix trailing spaces, indentation, and import ordering - Resolve remaining linting issues for CI pipeline This commit applies automatic formatting fixes to ensure all e2e security test files pass the CI linting checks. fix: exclude e2e-security from main ESLint to resolve CI import errors - Exclude test/e2e-security/**/* from main project ESLint configuration - Create separate ESLint config for e2e-security directory with proper import resolution - Remove e2e-security TypeScript config from main parser options - Add dedicated tsconfig.json for e2e-security tests This resolves CI linting failures caused by @playwright/test import resolution issues in the main project's ESLint configuration. fix: use yarn consistently and properly handle .yarn cache - Update GitHub Actions workflow to use yarn for e2e-security dependencies - Remove package-lock.json and use yarn.lock for e2e-security - Add .yarn/cache and install-state.gz to gitignore for e2e-security - Ensure consistent package manager usage across CI and local development This resolves package manager inconsistencies and follows yarn v3 best practices by excluding cache files while maintaining proper dependency management.
1 parent a2e6bab commit 9bf7819

21 files changed

+4778
-1
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"jasmine": true
44
},
55
"extends": "@wireapp/eslint-config",
6-
"ignorePatterns": ["**/*.js", "**/*.jsx"], // Ignore JS files until we migrate to TS
6+
"ignorePatterns": ["**/*.js", "**/*.jsx", "test/e2e-security/**/*"], // Ignore JS files until we migrate to TS
77
"overrides": [
88
{
99
"files": ["*.ts", "*.tsx"],
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
name: Security E2E Tests
2+
3+
on:
4+
push:
5+
branches: [main, staging, dev]
6+
pull_request:
7+
branches: [main, staging, dev]
8+
paths:
9+
- 'electron/src/**'
10+
- 'test/e2e-security/**'
11+
- '.github/workflows/security-tests.yml'
12+
13+
jobs:
14+
security-tests:
15+
name: Security E2E Tests
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 30
18+
19+
steps:
20+
- name: Cancel Previous Runs
21+
uses: styfle/[email protected]
22+
with:
23+
access_token: ${{github.token}}
24+
25+
- name: Checkout repository
26+
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 2
29+
30+
- name: Set up Node.js
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: 18.x
34+
cache: 'yarn'
35+
36+
- name: Install dependencies
37+
run: yarn --immutable
38+
39+
- name: Build Electron app
40+
run: yarn build:ts
41+
42+
- name: Install security test dependencies
43+
run: |
44+
cd test/e2e-security
45+
yarn install
46+
47+
- name: Install Playwright browsers
48+
run: |
49+
cd test/e2e-security
50+
yarn playwright install --with-deps
51+
52+
- name: Use xvfb-run on Linux
53+
run: |
54+
cd test/e2e-security
55+
sed -i 's/playwright test/xvfb-run playwright test/g' package.json
56+
57+
- name: Run Security Exposure Tests
58+
run: yarn test:security:exposure
59+
continue-on-error: false
60+
61+
- name: Run Security Validation Tests
62+
run: yarn test:security:validation
63+
continue-on-error: false
64+
65+
- name: Run Security Regression Tests
66+
run: yarn test:security:regression
67+
continue-on-error: false
68+
69+
- name: Upload Security Test Report
70+
if: always()
71+
uses: actions/upload-artifact@v4
72+
with:
73+
name: security-test-report
74+
path: test/e2e-security/security-test-report/
75+
retention-days: 30
76+
77+
- name: Upload Test Results
78+
if: always()
79+
uses: actions/upload-artifact@v4
80+
with:
81+
name: security-test-results
82+
path: test/e2e-security/test-results/
83+
retention-days: 30
84+
85+
- name: Comment PR with Security Test Results
86+
if: github.event_name == 'pull_request' && always()
87+
uses: actions/github-script@v7
88+
with:
89+
script: |
90+
const fs = require('fs');
91+
const path = require('path');
92+
93+
try {
94+
const reportPath = 'test/e2e-security/security-test-report/summary.json';
95+
if (fs.existsSync(reportPath)) {
96+
const summary = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
97+
98+
const comment = `## 🛡️ Security Test Results
99+
100+
**Context Isolation & Sandbox Validation**
101+
102+
- **Total Tests**: ${summary.totalTests || 'N/A'}
103+
- **Passed**: ${summary.passed || 'N/A'} ✅
104+
- **Failed**: ${summary.failed || 'N/A'} ${summary.failed > 0 ? '❌' : ''}
105+
- **Duration**: ${summary.duration || 'N/A'}ms
106+
107+
**Security Validations**
108+
- Context Isolation: ${summary.contextIsolationVerified ? '✅ Verified' : '❌ Failed'}
109+
- Sandbox Enforcement: ${summary.sandboxVerified ? '✅ Verified' : '❌ Failed'}
110+
111+
${summary.failed > 0 ? '⚠️ **Security tests failed!** Please review the test results and ensure all security measures are properly implemented.' : '🎉 **All security tests passed!** Context isolation and sandbox are working correctly.'}
112+
113+
[View detailed report](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})
114+
`;
115+
116+
github.rest.issues.createComment({
117+
issue_number: context.issue.number,
118+
owner: context.repo.owner,
119+
repo: context.repo.repo,
120+
body: comment
121+
});
122+
}
123+
} catch (error) {
124+
console.log('Could not read test summary:', error);
125+
}
126+
127+
security-audit:
128+
name: Security Audit
129+
runs-on: ubuntu-latest
130+
needs: security-tests
131+
if: always()
132+
133+
steps:
134+
- name: Checkout repository
135+
uses: actions/checkout@v4
136+
137+
- name: Set up Node.js
138+
uses: actions/setup-node@v4
139+
with:
140+
node-version: 18.x
141+
cache: 'yarn'
142+
143+
- name: Install dependencies
144+
run: yarn --immutable
145+
146+
- name: Run npm audit
147+
run: yarn audit --level moderate
148+
continue-on-error: true
149+
150+
- name: Run security linting
151+
run: |
152+
yarn eslint electron/src --ext .ts,.js --config .eslintrc.js --format json --output-file security-lint-report.json || true
153+
154+
- name: Upload Security Audit Results
155+
if: always()
156+
uses: actions/upload-artifact@v4
157+
with:
158+
name: security-audit-results
159+
path: |
160+
security-lint-report.json
161+
retention-days: 30

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,11 @@ resources
3333
!.yarn/releases
3434
!.yarn/sdks
3535
!.yarn/versions
36+
37+
# E2E test artifacts
38+
test/e2e-security/test-results/
39+
test/e2e-security/security-test-report/
40+
test/e2e-security/node_modules/
41+
test/e2e-security/.yarn/cache/
42+
test/e2e-security/.yarn/install-state.gz
43+
test-results/

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@
199199
"test:renderer": "electron-mocha --renderer --require .babel-register.js \"electron/src/**/*.test?(.renderer).ts\" --no-sandbox --window-config electron/test/mocha-window-config.json",
200200
"test:types": "tsc --noEmit",
201201
"test:react": "jest",
202+
"test:security": "cd test/e2e-security && npm run test:security",
203+
"test:security:exposure": "cd test/e2e-security && npm run test:security:exposure",
204+
"test:security:validation": "cd test/e2e-security && npm run test:security:validation",
205+
"test:security:regression": "cd test/e2e-security && npm run test:security:regression",
206+
"test:security:debug": "cd test/e2e-security && npm run test:security:debug",
207+
"test:security:install": "cd test/e2e-security && npm install && npm run test:security:install",
202208
"translate:upload": "ts-node -P tsconfig.bin.json ./bin/translations_upload.ts",
203209
"translate:download": "ts-node -P tsconfig.bin.json ./bin/translations_download.ts"
204210
},

test/e2e-security/.eslintrc.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"extends": ["../../.eslintrc.json"],
3+
"parserOptions": {
4+
"project": ["./tsconfig.json"]
5+
},
6+
"rules": {
7+
"no-console": "off",
8+
"valid-jsdoc": "off",
9+
"@typescript-eslint/no-unused-vars": "off",
10+
"no-unsanitized/property": "off",
11+
"import/no-unresolved": "off"
12+
},
13+
"settings": {
14+
"import/resolver": {
15+
"node": {
16+
"paths": ["node_modules", "../../node_modules"]
17+
}
18+
}
19+
}
20+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2025 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
19+
/**
20+
* MALICIOUS SCRIPT FOR SECURITY TESTING
21+
*
22+
* ⚠️ WARNING: This script contains malicious code patterns for testing purposes only!
23+
*
24+
* This script attempts various RCE (Remote Code Execution) techniques that should
25+
* be blocked by context isolation and sandbox enforcement.
26+
*
27+
* If any of these attempts succeed, it indicates a security vulnerability.
28+
*/
29+
30+
// Attempt 1: Direct require() access
31+
try {
32+
const fs = require('fs');
33+
window.__RCE_SUCCESS_FS = true;
34+
window.__RCE_FS_METHODS = Object.keys(fs);
35+
} catch (e) {
36+
window.__RCE_BLOCKED_FS = true;
37+
}
38+
39+
// Attempt 2: Process object access
40+
try {
41+
const proc = process;
42+
window.__RCE_SUCCESS_PROCESS = true;
43+
window.__RCE_PROCESS_VERSION = proc.version;
44+
window.__RCE_PROCESS_ENV = Object.keys(proc.env);
45+
} catch (e) {
46+
window.__RCE_BLOCKED_PROCESS = true;
47+
}
48+
49+
// Attempt 3: Child process execution
50+
try {
51+
const {exec} = require('child_process');
52+
exec('whoami', (error, stdout, stderr) => {
53+
window.__RCE_SUCCESS_EXEC = true;
54+
window.__RCE_EXEC_OUTPUT = stdout;
55+
});
56+
} catch (e) {
57+
window.__RCE_BLOCKED_EXEC = true;
58+
}
59+
60+
// Attempt 4: File system access
61+
try {
62+
const fs = require('fs');
63+
const content = fs.readFileSync('/etc/passwd', 'utf8');
64+
window.__RCE_SUCCESS_FILE_READ = true;
65+
window.__RCE_FILE_CONTENT = content;
66+
} catch (e) {
67+
window.__RCE_BLOCKED_FILE_READ = true;
68+
}
69+
70+
// Attempt 5: Network access to local services
71+
try {
72+
const net = require('net');
73+
const client = net.createConnection({port: 22}, () => {
74+
window.__RCE_SUCCESS_NET = true;
75+
});
76+
client.on('error', () => {
77+
window.__RCE_BLOCKED_NET = true;
78+
});
79+
} catch (e) {
80+
window.__RCE_BLOCKED_NET = true;
81+
}
82+
83+
// Attempt 6: Global object pollution
84+
try {
85+
global.__RCE_GLOBAL_POLLUTION = true;
86+
window.__RCE_SUCCESS_GLOBAL = true;
87+
} catch (e) {
88+
window.__RCE_BLOCKED_GLOBAL = true;
89+
}
90+
91+
// Attempt 7: Buffer access
92+
try {
93+
const buffer = Buffer.from('test');
94+
window.__RCE_SUCCESS_BUFFER = true;
95+
window.__RCE_BUFFER_SIZE = buffer.length;
96+
} catch (e) {
97+
window.__RCE_BLOCKED_BUFFER = true;
98+
}
99+
100+
// Attempt 8: Module loading
101+
try {
102+
const path = require('path');
103+
window.__RCE_SUCCESS_PATH = true;
104+
window.__RCE_PATH_SEP = path.sep;
105+
} catch (e) {
106+
window.__RCE_BLOCKED_PATH = true;
107+
}
108+
109+
// Attempt 9: OS information access
110+
try {
111+
const os = require('os');
112+
window.__RCE_SUCCESS_OS = true;
113+
window.__RCE_OS_PLATFORM = os.platform();
114+
window.__RCE_OS_HOSTNAME = os.hostname();
115+
} catch (e) {
116+
window.__RCE_BLOCKED_OS = true;
117+
}
118+
119+
// Attempt 10: Crypto access
120+
try {
121+
const crypto = require('crypto');
122+
window.__RCE_SUCCESS_CRYPTO = true;
123+
window.__RCE_CRYPTO_METHODS = Object.keys(crypto);
124+
} catch (e) {
125+
window.__RCE_BLOCKED_CRYPTO = true;
126+
}
127+
128+
// Report results
129+
window.__RCE_TEST_COMPLETE = true;
130+
window.__RCE_RESULTS = {
131+
fs: window.__RCE_SUCCESS_FS || false,
132+
process: window.__RCE_SUCCESS_PROCESS || false,
133+
exec: window.__RCE_SUCCESS_EXEC || false,
134+
fileRead: window.__RCE_SUCCESS_FILE_READ || false,
135+
net: window.__RCE_SUCCESS_NET || false,
136+
global: window.__RCE_SUCCESS_GLOBAL || false,
137+
buffer: window.__RCE_SUCCESS_BUFFER || false,
138+
path: window.__RCE_SUCCESS_PATH || false,
139+
os: window.__RCE_SUCCESS_OS || false,
140+
crypto: window.__RCE_SUCCESS_CRYPTO || false,
141+
};
142+
143+
console.log('RCE Test Results:', window.__RCE_RESULTS);

0 commit comments

Comments
 (0)