Architecture
ShipCode is a Turborepo monorepo with multiple apps and shared packages.
Structure
apps/
web/ Marketing site (Next.js + Tailwind)
desktop/ Electron + Vite + React 19
cli/ CLI tool (published as 'shipcode' on npm)
docs/ This documentation (Nextra)
packages/
pipeline/ Pipeline state machine
shared/ Types, schemas, constants, design tokens
agents/ Process manager, prompts, GitHub CLI
db/ SQLite persistence (node:sqlite)
git/ Git operations + worktree manager
ui/ React componentsKey Design Decisions
ghCLI over GitHub API — no OAuth, uses existing auth- Adversarial review — different model families catch different blind spots
- PR is the human gate — fully autonomous until PR creation
- Fork-point SHA for diffs — stable diffs even when base branch moves
- node:sqlite — built-in Node.js SQLite, no native addon dependencies
- Sandboxed Electron renderers — Chromium sandbox + context isolation around a narrow preload bridge
Desktop IPC Trust Boundary
Electron’s main process treats the registered channel name and the
IpcMainInvokeEvent as trusted application-owned values. Every argument received from the
renderer is untrusted, even though the preload bridge and TypeScript channel map constrain normal
callers.
Before any invoke handler runs, the shared main-process boundary requires either no argument or one plain object containing only bounded primitive values, arrays, and plain objects. It rejects accessors, symbol properties, circular references, prototype-sensitive keys, non-finite numbers, unsupported object prototypes, and payloads outside these limits:
- 1 MiB total input
- 512 KiB per string
- 20 nesting levels and 20,000 values
- 10,000 array items and 1,000 properties per object
- 256 bytes per property name
Transport validation is the first layer. Each handler still owns domain validation such as enum membership, project and thread existence, authorization, URL allowlists, and filesystem path safety. Failures are logged in full in the main process, while renderer-visible errors and IPC event metadata are reduced to one line and 280 characters.
Electron renderer boundary
ShipCode enables Electron’s Chromium sandbox before the app becomes ready and
also opts the main and splash windows into sandboxing explicitly. The main
renderer has Node.js integration disabled in the page, workers, and subframes;
context isolation remains enabled; insecure mixed content and <webview> are
disabled. The bundled preload remains the only renderer bridge and exposes the
same frozen { invoke, on } API through contextBridge.
Defense in depth does not rely on sandboxing alone:
- production uses a
default-src 'none'Content Security Policy with explicit resource directives; development adds only the Vite server allowances; - main-window navigation is restricted to the exact bundled renderer file (or the Vite development origin);
- renderer-created windows are denied, and HTTP(S) links are handed to the OS browser instead of inheriting ShipCode’s preload bridge;
- focused main-process tests pin the security-sensitive
BrowserWindowpreferences, CSP, navigation rules, and preload bridge behavior.