Skip to content

feat(security): atomic writes, input validators, and static analysis tooling#118

Open
brooksc wants to merge 1 commit into
johannesjo:mainfrom
brooksc:coordinator-1-security
Open

feat(security): atomic writes, input validators, and static analysis tooling#118
brooksc wants to merge 1 commit into
johannesjo:mainfrom
brooksc:coordinator-1-security

Conversation

@brooksc
Copy link
Copy Markdown
Contributor

@brooksc brooksc commented May 16, 2026

Overview

Standalone security utilities and static analysis tooling — no coordinator logic, no UI. This is PR 1 of 4 splitting #100 as requested in the round-4 review.

PR sequence:

PR Branch Contents
1 (this PR) coordinator-1-security Atomic writes, input validators, static analysis configs
2 coordinator-2-mcp-backend MCP coordinator engine + REST API hardening
3 coordinator-3-store-ipc Frontend store wiring + IPC handlers
4 coordinator-4-ui UI components + coordinator entry points

PRs 2–4 are stacked on each other; nothing coordinator-related is user-visible until PR 4 adds the NewTaskDialog checkbox and Settings toggle.


What's in this PR

electron/mcp/atomic.ts

Crash-safe file writes via temp file + rename:

  • Temp file written to same directory as target (avoids EXDEV across filesystem boundaries)
  • fsync before rename for file durability; directory fsync after rename for directory-entry durability (platform-safe catch for Windows/network mounts)
  • Sync variant for use in catch blocks; async variant for normal paths

electron/mcp/validation.ts

  • validateBranchName() — mirrors git check-ref-format --branch: rejects shell metacharacters (:, ^, ~, etc.), double dots, path components starting with ., any path component ending in .lock, and other git-illegal patterns
  • validateUUID() — enforces v4 UUID (version nibble=4, variant nibble∈{8,9,a,b})

Static analysis configs

  • .semgrep/ — rules for unsafe Electron APIs, IPC auth, and unsafe fs operations (scoped to electron/mcp/** + electron/ipc/register.ts)
  • .gitleaks.toml — token/secret leak detection
  • .dependency-cruiser.cjs — architecture linting
  • knip.config.ts — dead code detection

Note: the static analysis tools (semgrep, gitleaks, dependency-cruiser, knip) are not yet wired into CI — they require system/devDependency setup that lands in PR 2. Configs are included here as they're used to validate the coordinator code in PR 2.

Tests (35 cases)

  • electron/mcp/validation.test.ts — accepted names, all rejection cases, UUID v4 enforcement
  • electron/mcp/atomic.test.ts — write, overwrite, mode, no temp-file residue

Test plan

  • npm run typecheck && npm test && npm run lint — all pass
  • electron/mcp/validation.test.ts and electron/mcp/atomic.test.ts — 35/35

🤖 Generated with Claude Code

…tooling

Standalone security utilities with no coordinator dependencies — intended as
the foundation PR before the coordinator engine lands.

### atomic.ts
Crash-safe file writes via temp file + rename:
- Temp file written to same directory as target (avoids EXDEV across mounts)
- fsync before rename for file durability
- Directory fsync after rename for directory-entry durability (platform-safe catch)
- Sync variant for use in catch blocks; async variant for normal paths

### validation.ts
- validateBranchName(): mirrors git check-ref-format --branch rules — rejects shell
  metacharacters (:, ^, ~, etc.), double dots, path components starting with ".",
  any path component ending in ".lock", and other git-illegal patterns
- validateUUID(): enforces v4 UUID format (version nibble=4, variant nibble∈{8,9,a,b})

### Static analysis configs
- .semgrep/: rules for unsafe Electron APIs, IPC auth, and filesystem operations
  scoped to electron/mcp/** and electron/ipc/register.ts
- .gitleaks.toml: token/secret leak detection with ASCII quote character classes
- .dependency-cruiser.cjs: architecture linting with orphan exemptions for new files
- knip.config.ts: dead code detection

### Tests (35 cases)
- electron/mcp/validation.test.ts: accepted names, all rejection cases, UUID v4 format
- electron/mcp/atomic.test.ts: write, overwrite, mode, no temp-file residue

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@johannesjo
Copy link
Copy Markdown
Owner

Thanks for splitting this out. I took another careful pass over the PR and found a few things worth addressing before merge:

  1. electron/mcp/atomic.ts:50 / electron/mcp/atomic.ts:83 - overwriting an existing file without options.mode replaces the old inode with a temp file created under the process umask. That means an existing 0600 token/config file can silently become 0644 after an atomic overwrite. I reproduced this locally. Please preserve the existing target mode by default when the file exists, or default these helpers to a restrictive mode for security-sensitive writes, and add a regression test for overwriting a 0600 file.

  2. electron/mcp/validation.ts:4 - validateBranchName('HEAD') currently passes, but git check-ref-format --branch HEAD rejects it. Since this helper is documented as mirroring Git branch validation, please reject HEAD explicitly and add a test case.

  3. .semgrep/filesystem-safety.yml:3 - direct-writefile-in-mcp-coordinator only matches unqualified writeFileSync(...), but the code this rule is scoped to uses fs.writeFileSync(...) (electron/ipc/register.ts:586 is an existing example). As written, this guardrail will miss the main pattern it is intended to catch. Please add a fs.writeFileSync(...) pattern, likely via pattern-either.

  4. Non-blocking but confusing: the new Semgrep/Gitleaks/Knip/dependency-cruiser configs are not installed, scripted, or run in CI yet (package.json scripts and .github/workflows/ci.yml only run typecheck/lint/format). The PR body calls this out for CI, but knip.config.ts:7 also references electron/mcp/server.ts, which does not exist in this PR. If these configs are intentionally staged for the next PR, please either add a clear TODO/comment in the config or defer future-file references to the PR that introduces them.

Verification run locally on the PR head:

  • npm run compile passed
  • npm run lint passed
  • npm run typecheck passed
  • npm run format:check passed
  • npm test -- electron/mcp passed, 35 tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants