Skip to content

Commit c80a029

Browse files
brookscclaude
andcommitted
chore: add static analysis tooling (Knip, dependency-cruiser, Semgrep, Gitleaks)
- Knip: dead-code detection; found 2 dead files + 18 unused exports (not removed, reported) - dependency-cruiser: enforces src/→electron boundary, no-circular, no-orphans - Semgrep: 9 custom rules covering token-in-URL, innerHTML XSS, eval, shell injection, pkill-f - Gitleaks: secret scanning with custom rules for MCP tokens and bearer tokens - Scripts: lint:dead, lint:arch, lint:security, lint:secrets, check:static in package.json - Pre-commit: Husky runs lint-staged + check on every commit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3df3570 commit c80a029

8 files changed

Lines changed: 1706 additions & 116 deletions

.dependency-cruiser.cjs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/** @type {import('dependency-cruiser').IConfiguration} */
2+
module.exports = {
3+
forbidden: [
4+
{
5+
name: 'no-renderer-importing-main',
6+
severity: 'error',
7+
comment:
8+
'Renderer (src/) must never import Electron main-process code. ' +
9+
'Use IPC channels instead.',
10+
from: { path: '^src/' },
11+
to: {
12+
path: '^electron/',
13+
// Allow importing the shared IPC channel enum (channels.ts is a pure enum, no Node/Electron deps)
14+
pathNot: '^electron/ipc/channels\\.ts',
15+
},
16+
},
17+
{
18+
name: 'no-mcp-importing-components',
19+
severity: 'error',
20+
comment: 'MCP coordinator must not import frontend components or store.',
21+
from: { path: '^electron/mcp/' },
22+
to: { path: '^src/(components|store|lib)/' },
23+
},
24+
{
25+
name: 'no-circular',
26+
severity: 'error',
27+
comment:
28+
'Circular dependencies break tree-shaking and make reasoning about startup order impossible.',
29+
from: {},
30+
to: { circular: true },
31+
},
32+
{
33+
name: 'no-orphans',
34+
severity: 'warn',
35+
comment: 'Orphan modules have no importers and no exports used elsewhere — likely dead code.',
36+
from: {
37+
orphan: true,
38+
// Test files, config files, entry points, and pure type modules are expected orphans.
39+
// Type-only modules (types.ts, *.d.ts) are consumed by TypeScript structurally — the
40+
// import graph doesn't capture all type-level usage, so they appear orphaned.
41+
pathNot: [
42+
'\\.test\\.(ts|tsx)$',
43+
'\\.config\\.(ts|js|cjs)$',
44+
'\\.d\\.ts$',
45+
'types\\.ts$',
46+
'types\\.(ts|tsx)$',
47+
'^src/main\\.tsx$',
48+
'^src/remote/main\\.tsx$',
49+
'^electron/main\\.ts$',
50+
'^electron/preload\\.cjs$',
51+
'^electron/mcp/server\\.ts$',
52+
// Vite ambient env declarations
53+
'^src/vite-env\\.d\\.ts$',
54+
],
55+
},
56+
to: {},
57+
},
58+
],
59+
60+
options: {
61+
doNotFollow: {
62+
path: 'node_modules',
63+
},
64+
moduleSystems: ['es6', 'cjs'],
65+
tsConfig: {
66+
fileName: 'tsconfig.json',
67+
},
68+
reporterOptions: {
69+
dot: {
70+
collapsePattern: 'node_modules/[^/]+',
71+
},
72+
archi: {
73+
collapsePattern: '^(node_modules|src/components)/[^/]+',
74+
},
75+
},
76+
},
77+
};

.gitleaks.toml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
title = "Gitleaks config for Parallel Code"
2+
3+
[extend]
4+
# Use the default Gitleaks ruleset as the base
5+
useDefault = true
6+
7+
[[rules]]
8+
id = "parallel-code-mcp-token"
9+
description = "Parallel Code MCP bearer token"
10+
regex = '''PARALLEL_CODE_MCP_TOKEN\s*[=:]\s*['""]?[A-Za-z0-9+/\-_]{20,}['""]?'''
11+
tags = ["token", "parallel-code"]
12+
13+
[[rules]]
14+
id = "bearer-token-in-url"
15+
description = "Bearer token embedded in a URL query parameter"
16+
regex = '''[?&]token=[A-Za-z0-9+/\-_]{20,}'''
17+
tags = ["token", "url"]
18+
[rules.allowlist]
19+
# Test fixture URLs and documentation are expected to have placeholder tokens
20+
regexes = [
21+
'''token=<[A-Za-z0-9_-]+>''', # placeholder like ?token=<your-token>
22+
'''token=test''', # obvious test value
23+
'''token=abc''', # obvious test value
24+
'''token=tok''', # obvious test value
25+
]
26+
27+
[[rules]]
28+
id = "anthropic-api-key"
29+
description = "Anthropic API key"
30+
regex = '''sk-ant-[A-Za-z0-9\-_]{40,}'''
31+
tags = ["api-key", "anthropic"]
32+
33+
[allowlist]
34+
description = "Global allowlist"
35+
paths = [
36+
# Lock files contain package hashes, not secrets
37+
'''package-lock\.json''',
38+
# Test fixture files with obviously fake values
39+
'''\.test\.(ts|tsx)$''',
40+
# The gitleaks config itself documents patterns
41+
'''\.gitleaks\.toml''',
42+
]
43+
commits = []

.semgrep/electron-security.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
rules:
2+
- id: no-inner-html-without-sanitize
3+
pattern: $EL.innerHTML = $VAL
4+
pattern-not: $EL.innerHTML = DOMPurify.sanitize(...)
5+
message: |
6+
Direct innerHTML assignment without DOMPurify.sanitize() risks XSS.
7+
Use the sanitizeHtml() helper or DOMPurify.sanitize() explicitly.
8+
languages: [typescript]
9+
severity: ERROR
10+
paths:
11+
include:
12+
- '**/src/**'
13+
14+
- id: no-eval
15+
pattern: eval($X)
16+
message: |
17+
eval() executes arbitrary code. Not allowed in Electron renderer or main process.
18+
languages: [typescript]
19+
severity: ERROR
20+
21+
- id: no-new-function
22+
pattern: new Function($X)
23+
message: |
24+
new Function() executes arbitrary code. Not allowed in Electron renderer or main process.
25+
languages: [typescript]
26+
severity: ERROR
27+
28+
- id: no-shell-true-in-spawn
29+
pattern: |
30+
child_process.spawn($CMD, $ARGS, {shell: true})
31+
message: |
32+
spawn() with shell:true enables shell injection. Use shell:false and
33+
pass arguments as an array.
34+
languages: [typescript]
35+
severity: ERROR
36+
paths:
37+
include:
38+
- '**/electron/**'
39+
40+
- id: pkill-dash-f-broad-kill
41+
pattern: $EXEC("pkill", ["-f", ...], ...)
42+
message: |
43+
pkill -f matches any process whose full command line contains the pattern,
44+
which can accidentally kill unrelated processes. Use kill by PID or docker stop.
45+
languages: [typescript]
46+
severity: WARNING
47+
paths:
48+
include:
49+
- '**/electron/**'

.semgrep/filesystem-safety.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
rules:
2+
- id: direct-writefile-in-mcp-coordinator
3+
pattern: writeFileSync($PATH, $DATA, ...)
4+
message: |
5+
Direct writeFileSync in coordinator code risks torn writes on crash.
6+
Use atomicWriteFileSync() from electron/mcp/atomic.ts instead.
7+
languages: [typescript]
8+
severity: WARNING
9+
paths:
10+
include:
11+
- '**/electron/mcp/coordinator.ts'
12+
- '**/electron/ipc/register.ts'
13+
14+
- id: copyfilesync-side-effect
15+
pattern: fs.copyFileSync($SRC, $DST)
16+
message: |
17+
fs.copyFileSync is a filesystem side effect. In StartMCPServer,
18+
ensure all pure computation (mcpConfig, mergedMcpJson) precedes
19+
any copyFileSync calls so validation failures don't leave residue.
20+
languages: [typescript]
21+
severity: INFO
22+
paths:
23+
include:
24+
- '**/electron/ipc/register.ts'

.semgrep/ipc-auth.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
rules:
2+
- id: token-embedded-in-url-template
3+
pattern: |
4+
`$PREFIX?token=${$TOKEN}$SUFFIX`
5+
message: |
6+
Token embedded directly in URL template literal. Mobile/shared URLs must use
7+
mobileToken, not the coordinator token. The coordinator token must never appear
8+
in any URL that reaches the renderer or network.
9+
languages: [typescript]
10+
severity: ERROR
11+
paths:
12+
include:
13+
- '**/electron/**'
14+
15+
- id: console-log-token-variable
16+
pattern-either:
17+
- pattern: console.log($A, token, $B)
18+
- pattern: console.warn($A, token, $B)
19+
- pattern: console.log(token)
20+
- pattern: console.warn(token)
21+
message: |
22+
Logging a variable named 'token' directly. Use redactServerUrl() or
23+
ensure this is not a bearer token value.
24+
languages: [typescript]
25+
severity: WARNING
26+
paths:
27+
include:
28+
- '**/electron/**'

knip.config.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { KnipConfig } from 'knip';
2+
3+
const config: KnipConfig = {
4+
entry: [
5+
'electron/main.ts',
6+
'electron/preload.cjs',
7+
'electron/mcp/server.ts',
8+
'src/main.tsx',
9+
'src/remote/main.tsx',
10+
],
11+
project: ['electron/**/*.ts', 'src/**/*.{ts,tsx}'],
12+
ignore: [
13+
'dist/**',
14+
'dist-electron/**',
15+
'dist-remote/**',
16+
'release/**',
17+
'scripts/**',
18+
// Vite/Electron configs are entry points picked up by their respective tools
19+
'electron/vite.config.electron.ts',
20+
'electron/vite.config.electron.test.ts',
21+
'electron/shims/**',
22+
],
23+
ignoreDependencies: [
24+
// Peer dependencies and indirect runtime deps
25+
'electron',
26+
'electron-builder',
27+
'concurrently',
28+
'wait-on',
29+
],
30+
// Test files are allowed to have unused exports (test helpers, fixtures)
31+
ignoreExportsUsedInFile: true,
32+
};
33+
34+
export default config;

0 commit comments

Comments
 (0)