From cd6ea45972bb4ec8ebc7555c29ffc90faf2a7d7c Mon Sep 17 00:00:00 2001 From: Leonardo Grasso Date: Wed, 15 Apr 2026 15:24:59 +0200 Subject: [PATCH 1/6] feat: Windows support Co-Authored-By: Loris Degioanni Signed-off-by: Leonardo Grasso --- CLAUDE.md | 6 +- README.md | 36 +- hooks/claude-code/Cargo.lock | 364 ++++++++++++++++++ hooks/claude-code/Cargo.toml | 3 + hooks/claude-code/src/main.rs | 46 ++- installers/windows/Package.wxs | 141 +++++++ installers/windows/README.md | 243 ++++++++++++ installers/windows/build-falco.ps1 | 262 +++++++++++++ .../windows/coding-agents-kit-launcher.ps1 | 79 ++++ .../windows/falco-windows-curl-noproxy.patch | 64 +++ .../windows/falco-windows-http-output.patch | 66 ++++ installers/windows/license.rtf | 11 + installers/windows/package.ps1 | 303 +++++++++++++++ installers/windows/scripts/postinstall.ps1 | 131 +++++++ installers/windows/scripts/uninstall.ps1 | 35 ++ plugins/coding-agent-plugin/Cargo.lock | 312 ++++++++++++++- plugins/coding-agent-plugin/Cargo.toml | 3 + plugins/coding-agent-plugin/src/broker.rs | 17 +- plugins/coding-agent-plugin/src/config.rs | 21 +- plugins/coding-agent-plugin/src/event.rs | 108 +++++- .../coding-agent-plugin/src/socket_server.rs | 71 +++- tests/mock_broker_uds.ps1 | 157 ++++++++ tests/test_e2e_windows.ps1 | 340 ++++++++++++++++ tests/test_installed_service_windows.ps1 | 179 +++++++++ tests/test_interceptor_windows.ps1 | 242 ++++++++++++ tools/coding-agents-kit-ctl/src/main.rs | 287 ++++++++++++-- 26 files changed, 3465 insertions(+), 62 deletions(-) create mode 100644 installers/windows/Package.wxs create mode 100644 installers/windows/README.md create mode 100644 installers/windows/build-falco.ps1 create mode 100644 installers/windows/coding-agents-kit-launcher.ps1 create mode 100644 installers/windows/falco-windows-curl-noproxy.patch create mode 100644 installers/windows/falco-windows-http-output.patch create mode 100644 installers/windows/license.rtf create mode 100644 installers/windows/package.ps1 create mode 100644 installers/windows/scripts/postinstall.ps1 create mode 100644 installers/windows/scripts/uninstall.ps1 create mode 100644 tests/mock_broker_uds.ps1 create mode 100644 tests/test_e2e_windows.ps1 create mode 100644 tests/test_installed_service_windows.ps1 create mode 100644 tests/test_interceptor_windows.ps1 diff --git a/CLAUDE.md b/CLAUDE.md index ce12fdd..7670ab8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **coding-agents-kit** is a runtime security layer for AI coding agents. It intercepts tool calls (shell commands, file writes, web requests, etc.) before execution, evaluates them against Falco security rules, and enforces allow/deny/ask verdicts in real time. It operates entirely in user space with no elevated privileges. -The initial version targets **Claude Code** on **Linux and macOS**. The architecture is designed to accommodate other coding agents (e.g., Codex) in the future. +The project targets **Claude Code** on **Linux, macOS, and Windows**. The architecture is designed to accommodate other coding agents (e.g., Codex) in the future. ## Architecture @@ -37,7 +37,7 @@ The initial version targets **Claude Code** on **Linux and macOS**. The architec | **Interceptor** | `hooks/claude-code/` | Rust | Thin passthrough: reads hook JSON from stdin, wraps in envelope, sends to broker, maps verdict to stdout. No content interpretation. | | **Plugin** | `plugins/` | Rust (falco_plugin SDK) | Falco source+extract plugin with embedded broker. Parses events, extracts fields, feeds Falco, receives alerts, resolves verdicts. | | **Rules** | `rules/` | YAML (Falco rule language) | Vendor and local security policies. | -| **Installer** | `installers/linux/`, `installers/macos/` | Shell | Platform-specific packaging, installation, hook registration, mode switching. | +| **Installer** | `installers/linux/`, `installers/macos/`, `installers/windows/` | Shell/PowerShell | Platform-specific packaging, installation, hook registration, mode switching. | | **Skills** | `skills/` | Claude Code skill format | Coding agent skills for rule authoring, status, etc. | | **Tests** | `tests/` | TBD | Integration and E2E tests. | @@ -283,7 +283,7 @@ The macOS implementation includes `is_service_loaded()` for idempotent start/sto - **Falco 0.43** — rule engine, running in `nodriver` mode (no kernel instrumentation) - **Rust** — interceptor and plugin (using `falco_plugin` crate v0.5.0) -- **Platforms** — Linux (official Falco builds), macOS (Falco built from source with http_output patch) +- **Platforms** — Linux (official Falco builds), macOS (Falco built from source with http_output patch), Windows (Falco built from source with http_output patch, system curl via vcpkg) ## Build & Development diff --git a/README.md b/README.md index 69a7bf1..92cb7b3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Sandbox](https://img.shields.io/badge/status-sandbox-red?style=for-the-badge)](https://github.com/falcosecurity/evolution/blob/main/REPOSITORIES.md#sandbox) [![License](https://img.shields.io/github/license/leogr/coding-agents-kit?style=flat-square)](LICENSE) -![Platforms](https://img.shields.io/badge/platforms-linux%20%7C%20macOS-blue?style=flat-square) +![Platforms](https://img.shields.io/badge/platforms-linux%20%7C%20macOS%20%7C%20Windows-blue?style=flat-square) ![Architectures](https://img.shields.io/badge/arch-x86__64%20%7C%20aarch64-blueviolet?style=flat-square) > **Experimental Preview** — This project is under active development and released as an early preview. Interfaces and behavior may change between releases. We welcome your [feedback](#feedback) to help shape its future. @@ -74,6 +74,19 @@ bash install.sh The installer copies all components to `~/.coding-agents-kit/`, starts a systemd user service, and registers the hook automatically. +### Windows + +Download the `.msi` installer from the [latest release](https://github.com/leogr/coding-agents-kit/releases/latest) and run: + +```powershell +powershell -File Install-CodingAgentsKit.ps1 +``` + +The installer deploys all components to `%LOCALAPPDATA%\coding-agents-kit\`, registers the Claude Code hook, and sets up auto-start on login. + +> [!NOTE] +> x86_64 builds work on both x86_64 and ARM64 Windows (via emulation). See [`installers/windows/`](installers/windows/) for build prerequisites and details. + ### Verify ```bash @@ -184,10 +197,10 @@ The skill guides Claude through writing the rule, placing it in the right direct |-------|----------|--------| | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Linux (x86_64, aarch64) | Supported | | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | macOS (Apple Silicon, Intel) | Supported | +| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Windows (x86_64, ARM64) | Supported | | [Codex](https://openai.com/index/codex/) | Linux, macOS | Planned | -| — | Windows | Planned | -We are actively working on expanding agent and platform support. [Codex](https://openai.com/index/codex/) integration and Windows support are next on the roadmap. +We are actively working on expanding agent and platform support. [Codex](https://openai.com/index/codex/) integration is next on the roadmap. ## Building from Source @@ -233,6 +246,23 @@ See [`installers/macos/`](installers/macos/) for details. +
+Windows + +Requires: Rust (latest stable), Visual Studio 2022+ with C++ workload, CMake 3.24+, vcpkg with curl, .NET Runtime 8+, WiX Toolset v4. + +```powershell +powershell -File installers\windows\package.ps1 -Version 0.1.0 +``` + +Output: `build/out/coding-agents-kit-0.1.0-windows-x64.msi` + +> Falco is built from source on the first run (~10 min). Subsequent builds use the cached binary. + +See [`installers/windows/`](installers/windows/) for detailed prerequisites and build options. + +
+
Individual Components diff --git a/hooks/claude-code/Cargo.lock b/hooks/claude-code/Cargo.lock index 2173a33..4c97fd6 100644 --- a/hooks/claude-code/Cargo.lock +++ b/hooks/claude-code/Cargo.lock @@ -2,12 +2,117 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "claude-interceptor" version = "0.1.0" dependencies = [ "serde", "serde_json", + "uds_windows", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -16,12 +121,61 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -40,6 +194,31 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -94,12 +273,197 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys", +] + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/hooks/claude-code/Cargo.toml b/hooks/claude-code/Cargo.toml index a41d157..6c1b44c 100644 --- a/hooks/claude-code/Cargo.toml +++ b/hooks/claude-code/Cargo.toml @@ -12,6 +12,9 @@ path = "src/main.rs" serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["raw_value"] } +[target.'cfg(windows)'.dependencies] +uds_windows = "1.2" + [profile.release] opt-level = "z" lto = true diff --git a/hooks/claude-code/src/main.rs b/hooks/claude-code/src/main.rs index e36c83b..0af1816 100644 --- a/hooks/claude-code/src/main.rs +++ b/hooks/claude-code/src/main.rs @@ -25,8 +25,8 @@ use serde::{Deserialize, Serialize}; use std::env; use std::io::{self, BufRead, Read, Write}; +#[cfg(unix)] use std::net::Shutdown; -use std::os::unix::net::UnixStream; use std::process; use std::time::{Duration, Instant}; @@ -84,6 +84,7 @@ struct HookSpecificOutput<'a> { const DEFAULT_TIMEOUT_MS: u64 = 5000; const TIMEOUT_MIN_MS: u64 = 100; const TIMEOUT_MAX_MS: u64 = 30000; +#[cfg(unix)] const SOCKET_SUFFIX: &str = "/.coding-agents-kit/run/broker.sock"; const INPUT_MAX: usize = 64 * 1024; const RESPONSE_MAX: u64 = 64 * 1024; @@ -144,11 +145,24 @@ fn get_socket_path() -> String { return v; } } - let home = env::var("HOME").unwrap_or_default(); - if home.is_empty() { - return String::new(); + #[cfg(unix)] + { + let home = env::var("HOME").unwrap_or_default(); + if home.is_empty() { + return String::new(); + } + format!("{home}{SOCKET_SUFFIX}") + } + #[cfg(windows)] + { + // MSI installs to %LOCALAPPDATA%\coding-agents-kit (not %USERPROFILE%) + let base = env::var("LOCALAPPDATA").unwrap_or_default(); + if base.is_empty() { + return String::new(); + } + // Use forward slashes — YAML configs and AF_UNIX paths must match exactly. + format!("{}/coding-agents-kit/run/broker.sock", base.replace('\\', "/")) } - format!("{home}{SOCKET_SUFFIX}") } fn get_timeout() -> Duration { @@ -176,8 +190,12 @@ fn remaining_timeout(start: Instant, timeout: Duration) -> Result Result { let start = Instant::now(); - let stream = - UnixStream::connect(socket_path).map_err(|e| format!("broker unavailable: {e}"))?; + #[cfg(unix)] + let stream = std::os::unix::net::UnixStream::connect(socket_path) + .map_err(|e| format!("broker unavailable: {e}"))?; + #[cfg(windows)] + let stream = uds_windows::UnixStream::connect(socket_path) + .map_err(|e| format!("broker unavailable: {e}"))?; // Send request. let remaining = remaining_timeout(start, timeout)?; @@ -188,7 +206,11 @@ fn communicate(socket_path: &str, request: &[u8], timeout: Duration) -> Result Result<(), Error> { // Step 4: Configuration. let socket_path = get_socket_path(); if socket_path.is_empty() { - return Err(Error::BrokerError( - "HOME not set, cannot locate broker socket".into(), - )); + #[cfg(unix)] + let msg = "HOME not set, cannot locate broker socket"; + #[cfg(windows)] + let msg = "LOCALAPPDATA not set, cannot locate broker socket"; + return Err(Error::BrokerError(msg.into())); } let timeout = get_timeout(); diff --git a/installers/windows/Package.wxs b/installers/windows/Package.wxs new file mode 100644 index 0000000..6a974cd --- /dev/null +++ b/installers/windows/Package.wxs @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installers/windows/README.md b/installers/windows/README.md new file mode 100644 index 0000000..ae35983 --- /dev/null +++ b/installers/windows/README.md @@ -0,0 +1,243 @@ +# Windows Build & Installation Guide + +## Architecture + +### Platform + +Builds target the native architecture of the machine. The build scripts auto-detect x86_64 or ARM64 and compile accordingly. + +### Pipeline + +The Windows port uses the same Falco plugin architecture as Linux/macOS. Alert delivery uses Falco's native `http_output` on all platforms: + +``` +Claude Code + │ PreToolUse hook + ▼ +claude-interceptor.exe ──Unix socket──▶ Plugin broker (in Falco) + │ + ▼ + Falco rule engine + │ + ▼ + http_output (curl) + │ + ▼ + Plugin HTTP server + │ + ▼ + Verdict resolution + │ + ▼ + Response to interceptor +``` + +| Component | Linux/macOS | Windows | +|-----------|------------|---------| +| Interceptor → broker | Unix domain socket | Unix domain socket (via `uds_windows` crate) | +| Broker | Embedded in plugin | Embedded in plugin | +| Plugin | .so / .dylib loaded by Falco | .dll loaded by Falco | +| Alert delivery | Falco `http_output` (curl) | Falco `http_output` (curl, patched in) | +| Processes | 1 (Falco) | 1 (Falco) | + +### http_output on Windows + +Falco's upstream build excludes `http_output` on Windows (CMake and preprocessor guards). The `falco-windows-http-output.patch` enables it using the same `HAS_HTTP_OUTPUT` pattern as the macOS patch. System curl is provided via vcpkg with SChannel backend (no OpenSSL dependency). + +A second patch (`falco-windows-curl-noproxy.patch`) handles two SChannel-specific curl limitations: + +1. **NOPROXY**: Adds `CURLOPT_NOPROXY="*"` to bypass system/environment proxy settings for localhost alert delivery. Tolerates `CURLE_NOT_BUILT_IN` because the SChannel curl backend may omit proxy support — if proxy support is absent there is no proxy to bypass anyway. + +2. **CA path**: Wraps the CA certificate path/bundle options in a Windows-specific block that tolerates `CURLE_NOT_BUILT_IN`. SChannel uses the Windows Certificate Store automatically and does not support `CURLOPT_CAPATH` / `CURLOPT_CAINFO`. The original `CHECK_RES` macro would propagate this error and prevent `http_output` from initializing. For plain HTTP delivery to localhost no CA verification is needed regardless. + +## Prerequisites + +Install the following in order. All commands should be run from PowerShell. + +### 1. Visual Studio Build Tools + +Install [Visual Studio 2022+](https://visualstudio.microsoft.com/) (Community is free) with: +- **Desktop development with C++** workload + - MSVC v143+ build tools + - Windows SDK (10.0 or later) + - C++ CMake tools for Windows + +Verify: +```powershell +# Open "Developer Command Prompt for VS" and run: +cl +cmake --version # requires 3.24+ +``` + +### 2. Git + +Install [Git for Windows](https://git-scm.com/download/win). + +```powershell +git --version +``` + +### 3. Rust + +```powershell +Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe +.\rustup-init.exe -y +# Restart your shell, then: +rustc --version # requires 1.80+ +cargo --version + +# Add x64 target (needed if building on ARM64 Windows) +rustup target add x86_64-pc-windows-msvc +``` + +### 4. vcpkg + curl + +vcpkg provides the system curl library used by Falco's `http_output`. + +```powershell +git clone https://github.com/microsoft/vcpkg +.\vcpkg\bootstrap-vcpkg.bat +.\vcpkg\vcpkg install curl:x64-windows-static +# Set VCPKG_ROOT so the build script finds it: +$env:VCPKG_ROOT = (Resolve-Path .\vcpkg).Path +``` + +Note: on recent vcpkg baselines, the `schannel` feature name is not required for this use case. The packaging/build scripts only require `libcurl.lib` to be present under the selected triplet. + +### 5. .NET Runtime + WiX Toolset (for MSI packaging) + +```powershell +winget install Microsoft.DotNet.Runtime.9 --accept-package-agreements --accept-source-agreements +dotnet tool install --global wix +wix eula accept wix7 +wix --version +``` + +WiX v7 requires explicit OSMF EULA acceptance. Without this, packaging fails with `WIX7015`. + +## Building + +### Quick Build (all components + MSI) + +```powershell +# From the repository root: +powershell -File installers\windows\package.ps1 -Version 0.1.2 +``` + +This single command: +1. Compiles all Rust crates for x64 (interceptor, plugin DLL, ctl) +2. Clones and builds Falco 0.43.0 from source with `http_output` patch (~10 min, cached) +3. Patches `falco.exe` plugin search path +4. Stages all files and produces the MSI installer + +Output: `build/out/coding-agents-kit-0.1.2-windows-x64.msi` + +### Step-by-Step Build + +```powershell +# 1. Build Rust crates (targets x64) +cd hooks\claude-code && cargo build --release --target x86_64-pc-windows-msvc && cd ..\.. +cd plugins\coding-agent-plugin && cargo build --release --target x86_64-pc-windows-msvc && cd ..\.. +cd tools\coding-agents-kit-ctl && cargo build --release --target x86_64-pc-windows-msvc && cd ..\.. + +# 2. Build Falco from source (~10 minutes first time, cached after) +powershell -File installers\windows\build-falco.ps1 -Arch x64 + +# 3. Package MSI (uses pre-built components) +powershell -File installers\windows\package.ps1 -Version 0.1.2 -SkipRustBuild -SkipFalcoBuild +``` + +### Build Options + +| Flag | Description | +|------|-------------| +| `-Version 0.1.2` | Package version (semantic versioning) | +| `-Arch x64` | Target architecture (default: x64) | +| `-SkipRustBuild` | Skip Rust compilation (use pre-built binaries) | +| `-SkipFalcoBuild` | Skip Falco build (use cached build) | +| `-FalcoExe ` | Use a specific pre-built `falco.exe` | + +## Installing + +```powershell +# Install (deploys files + runs postinstall setup deterministically) +powershell -File build\out\Install-CodingAgentsKit.ps1 + +# Or run MSI directly (shows a standard wizard with acceptance notice) +msiexec /i build\out\coding-agents-kit-0.1.2-windows-x64.msi +``` + +The installer: +- Deploys binaries, plugin DLL, rules, and scripts to `%LOCALAPPDATA%\coding-agents-kit\` +- Generates Falco configuration with resolved Windows paths +- Registers the Claude Code interceptor hook +- Sets up auto-start via Registry Run key + +The MSI wizard now includes an acceptance notice that explains the hook, auto-start, and fail-closed behavior before installation continues. + +If the product is already installed, `Install-CodingAgentsKit.ps1` opens the MSI maintenance UI instead of forcing a silent reinstall. + +For unattended/silent automation, prefer `Install-CodingAgentsKit.ps1` so post-install tasks are always executed in user context and the script prints a final completion line. + +If you run the helper script, it prints a final completion line: `coding-agents-kit installation complete`. + +### Uninstalling + +**Always use the uninstall helper script** — it runs the cleanup script, removes the Claude Code hook, removes the auto-start registry key, and then removes the MSI-managed files: + +```powershell +powershell -File Uninstall-CodingAgentsKit.ps1 +``` + +> **Warning**: Do NOT uninstall via Apps & Features or `msiexec /x` directly. The MSI alone cannot run cleanup scripts (Windows limitation for per-user installs), so the Claude Code hook and auto-start registry key would be left behind, leaving Claude Code in a fail-closed state. + +## Running Tests + +```powershell +# Interceptor tests (17 test cases, no Falco needed) +powershell -File tests\test_interceptor_windows.ps1 + +# End-to-end tests (requires built Falco + plugin) +powershell -File tests\test_e2e_windows.ps1 +``` + +## Directory Layout (installed) + +``` +%LOCALAPPDATA%\coding-agents-kit\ +├── bin\ +│ ├── falco.exe # Falco rule engine (x64) +│ ├── claude-interceptor.exe # Claude Code hook +│ ├── coding-agents-kit-ctl.exe # Service management CLI +│ └── coding-agents-kit-launcher.ps1 # Service launcher +├── share\ +│ └── coding_agent.dll # Falco plugin (source + extract) +├── config\ +│ ├── falco.yaml # Base Falco config +│ └── falco.coding_agents_plugin.yaml # Plugin + rules config +├── rules\ +│ ├── default\coding_agents_rules.yaml # Default security rules +│ ├── user\ # Custom user rules (preserved on upgrade) +│ └── seen.yaml # Catch-all rule (required) +├── scripts\ +│ ├── postinstall.ps1 # Post-install setup +│ └── uninstall.ps1 # Pre-uninstall cleanup +├── run\ # Runtime state +└── log\ # Falco log files +``` + +## Known Caveats + +- **`broker response timeout` / stale pending requests**: if Claude Code tool calls are denied with a broker timeout and Falco logs show `reaping stale pending request`, the alert round-trip is not completing. The most common cause is a misconfigured `http_output.url` in `falco.yaml` (must match the plugin's `http_port`) or an older Falco build without the SChannel curl fix in `falco-windows-curl-noproxy.patch`. Run `coding-agents-kit-ctl health` to confirm the pipeline is healthy after starting the service. + +- **ARM64 hosts and Rust/MSVC arch alignment**: on ARM64 Windows, use matching ARM64 toolchain for Rust host (`stable-aarch64-pc-windows-msvc`) and MSVC (`vcvarsall arm64`) when building ARM64 artifacts. Mixed host/target toolchains can fail with unresolved externals at link time. + +- **Falco nested CMake generator on ARM64 hosts**: Falco's `falcosecurity-libs` nested configure may choose a Visual Studio generator/platform different from the top-level build if generator/platform are not forwarded explicitly. Keep nested and top-level generator settings aligned to avoid `VCTargetsPath` / platform mismatch errors. + +- **Git Bash PATH shadowing**: Git Bash's `/usr/bin/tar` misinterprets Windows paths with `C:` as a remote host. The build scripts prepend `C:\Windows\System32` to PATH so Windows native `tar.exe` is used. If running build scripts manually from Git Bash, use: `powershell -File