From 327c10fd90a6186a9f035b4b6ddb5bbba1839e92 Mon Sep 17 00:00:00 2001 From: ezl-keygraph Date: Mon, 21 Sep 2026 18:16:22 +0530 Subject: [PATCH] chore: refuse native Windows, point to WSL2 setup guide (#467) --- CLAUDE.md | 2 +- apps/cli/src/config/resolver.ts | 15 ++++++--------- apps/cli/src/docker.ts | 3 --- apps/cli/src/index.ts | 13 +++++++++++++ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c668603c..9528f948 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -126,7 +126,7 @@ Infra (Temporal) runs via `docker-compose.yml`. Workers are ephemeral `docker ru - `docker-compose.yml` — Infra only: `shannon-temporal` (port 7233/8233). Network: `shannon-net` - `Dockerfile` — 2-stage build (builder + Chainguard Wolfi runtime). Uses pnpm. Entrypoint: `CMD ["node", "apps/worker/dist/temporal/worker.js"]` - No `docker-compose.docker.yml` — host gateway handled via `--add-host` flag in CLI -- `/etc/hosts` forwarding — at worker spawn, `forwardEtcHostsFlags` in `apps/cli/src/docker.ts` reads the host's `/etc/hosts` and emits one `--add-host` flag per valid user-added entry. Loopback IPs (`127.x`, `::1`) are rewritten to `host-gateway`; IPv6 addresses are bracketed. Disable per-scan via `SHANNON_FORWARD_HOSTS=false`. No-op on Windows native (WSL2 reads its own `/etc/hosts` via the Linux path). +- `/etc/hosts` forwarding — at worker spawn, `forwardEtcHostsFlags` in `apps/cli/src/docker.ts` reads the host's `/etc/hosts` and emits one `--add-host` flag per valid user-added entry. Loopback IPs (`127.x`, `::1`) are rewritten to `host-gateway`; IPv6 addresses are bracketed. Disable per-scan via `SHANNON_FORWARD_HOSTS=false`. Native Windows is refused at startup (`blockNativeWindows` in `apps/cli/src/index.ts`, pointing to the WSL2 guide in `docs/platforms.md`); WSL2 reads its own `/etc/hosts` via the Linux path. ### Worker Package (`apps/worker/`) - `apps/worker/src/paths.ts` — Centralized path constants (`PROMPTS_DIR`, `CONFIGS_DIR`, `WORKSPACES_DIR`) diff --git a/apps/cli/src/config/resolver.ts b/apps/cli/src/config/resolver.ts index aac9e66b..ce7c4886 100644 --- a/apps/cli/src/config/resolver.ts +++ b/apps/cli/src/config/resolver.ts @@ -96,15 +96,12 @@ function loadTOML(): TOMLConfig | null { if (!fs.existsSync(configPath)) return null; // Config contains secrets — refuse to read if group or others have any access. - // Skip on Windows where POSIX permissions are not supported. - if (process.platform !== 'win32') { - const mode = fs.statSync(configPath).mode; - if (mode & 0o077) { - const actual = (mode & 0o777).toString(8).padStart(3, '0'); - fail( - `Your config file is readable by other users on this machine (${actual}). Lock it down: chmod 600 ${configPath}`, - ); - } + const mode = fs.statSync(configPath).mode; + if (mode & 0o077) { + const actual = (mode & 0o777).toString(8).padStart(3, '0'); + fail( + `Your config file is readable by other users on this machine (${actual}). Lock it down: chmod 600 ${configPath}`, + ); } try { diff --git a/apps/cli/src/docker.ts b/apps/cli/src/docker.ts index 6720db00..641bd318 100644 --- a/apps/cli/src/docker.ts +++ b/apps/cli/src/docker.ts @@ -360,7 +360,6 @@ function shouldSkipHostsName(name: string, hostname: string): boolean { */ function forwardEtcHostsFlags(): string[] { if (!envBool('SHANNON_FORWARD_HOSTS', true)) return []; - if (os.platform() === 'win32') return []; let content: string; try { @@ -517,8 +516,6 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess { // ignore stdin/stdout (the container ID is noise). return spawn('docker', args, { stdio: ['ignore', 'ignore', 'inherit'], - // Prevent MSYS/Git Bash from converting Unix paths on Windows - ...(os.platform() === 'win32' && { env: { ...process.env, MSYS_NO_PATHCONV: '1' } }), }); } diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 85a27a99..66199c61 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -61,6 +61,18 @@ function blockSudo(): void { ); } +/** Refuse to run on native Windows. WSL2 reports `linux`, so it is unaffected. */ +function blockNativeWindows(): void { + if (process.platform !== 'win32') return; + + failWith( + 'CLI_PRECONDITION_FAILED', + 'Shannon does not run on native Windows.', + 'Run Shannon inside WSL2. Setup instructions:', + 'https://github.com/KeygraphHQ/shannon/blob/main/docs/platforms.md', + ); +} + /** Commands whose `--json` output contract extends to failures. */ const JSON_CAPABLE_COMMANDS = new Set(['status', 'scans', 'version', '--version', '-v']); @@ -262,6 +274,7 @@ async function main(): Promise { enableJsonErrors(); } + blockNativeWindows(); blockSudo(); const args = process.argv.slice(2);