feat: add windows support#17
Conversation
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io> chore: more golint issues - rebase this Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
6711936 to
f8a456c
Compare
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
There was a problem hiding this comment.
Pull request overview
Adds official Windows support across the agent runtime, detectors, output, and install/uninstall flows.
Changes:
- Introduces Windows-specific execution helpers (shell dispatching, process checks), device info collection, and scheduled-task based install/uninstall via
schtasks. - Extends multiple detectors (IDEs, MCP configs, AI tools, Node scans, frameworks) to work on Windows paths and conventions, with accompanying tests.
- Adds Windows build/deploy tooling and updates CLI/output labeling for Windows.
Reviewed changes
Copilot reviewed 38 out of 39 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_smoke_go.sh | Updates smoke test expectation for the CLI “unknown option” message. |
| scripts/deploy-windows.sh | New helper script to build, SCP, and optionally run the Windows binary remotely. |
| internal/telemetry/telemetry.go | Uses detected platform in telemetry payload; makes HTTP body closing explicit/ignored. |
| internal/telemetry/logcapture.go | Ignores pipe close errors during log capture finalization. |
| internal/schtasks/schtasks_test.go | Adds unit tests for Windows Task Scheduler install/uninstall helpers. |
| internal/schtasks/schtasks.go | Implements Windows scheduled scanning via schtasks (install/uninstall). |
| internal/output/pretty.go | Labels OS as Windows when applicable in pretty output. |
| internal/output/html_test.go | Adjusts test cleanup to ignore remove errors. |
| internal/output/html.go | Ignores file close errors; labels OS as Windows/macOS in template. |
| internal/lock/lock_windows.go | Adds Windows lockfile location + PID liveness check via tasklist. |
| internal/lock/lock_unix.go | Moves Unix lockfile location + PID liveness check behind !windows build tag. |
| internal/lock/lock.go | Refactors lockfile constant into per-OS files; ignores close error when writing PID. |
| internal/launchd/launchd.go | Ignores close/remove errors while installing/uninstalling launchd plist. |
| internal/executor/mock.go | Adds GOOS override setter for cross-platform tests. |
| internal/executor/executor_windows.go | Adds Windows implementations for IsRoot and RunAsUser. |
| internal/executor/executor_unix.go | Moves Unix implementations for IsRoot and RunAsUser behind !windows. |
| internal/executor/executor.go | Removes Unix-only IsRoot/RunAsUser from common file; keeps interface + shared ops. |
| internal/device/device_test.go | Adds Windows device-gathering unit test. |
| internal/device/device.go | Makes device gathering platform-aware; adds Windows serial/OS version collection. |
| internal/detector/shellcmd.go | Adds platform-aware shell execution and quoting helper. |
| internal/detector/process.go | Adds cross-platform process-running checks (pgrep vs tasklist). |
| internal/detector/nodescan_test.go | Adds Windows-focused tests for node global/project scans and path handling. |
| internal/detector/nodescan.go | Uses platform-aware shell runner/quoting; improves node_modules path detection. |
| internal/detector/nodeproject.go | Normalizes path slashes to detect bun install directory on Windows. |
| internal/detector/nodepm_test.go | Adds Windows tests for package manager detection and project PM detection. |
| internal/detector/mcp_test.go | Adds Windows test for MCP config path resolution via env expansion. |
| internal/detector/mcp.go | Adds Windows config path support; refactors config path resolution; tweaks JSONC comment stripping loop. |
| internal/detector/ide_test.go | Adds Windows tests for VS Code and Claude detection. |
| internal/detector/ide.go | Adds Windows IDE detection paths, binary/version detection, and registry-based version fallback. |
| internal/detector/framework_test.go | Adds Windows test for Ollama detection including tasklist-based running check. |
| internal/detector/framework.go | Adds Windows LM Studio detection path/version; uses cross-platform process checks. |
| internal/detector/extension.go | Uses filepath.Join for extension paths to support Windows separators. |
| internal/detector/aicli.go | Improves tilde expansion (filepath-safe), Windows .exe lookup, version normalization, env-path resolution, and temp home fallback. |
| internal/detector/agent_test.go | Adds Windows test for Claude Cowork detection/version via registry. |
| internal/detector/agent.go | Uses filepath.Join; adds Windows support for Claude Cowork detection/version. |
| internal/cli/cli.go | Normalizes “unknown option” error message; updates help text for scheduled scanning; ignores stdout write errors. |
| cmd/stepsecurity-dev-machine-guard/main.go | Routes enterprise install/uninstall to schtasks on Windows, launchd otherwise. |
| Makefile | Adds build-windows and deploy-windows targets. |
| .gitignore | Ignores built binaries more broadly, including *.exe. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if [[ -n "$KEY" ]]; then | ||
| scp $SSH_OPTS -i "$KEY" "$src" "${USER}@${HOST}:${dst}" | ||
| elif [[ -n "$PASSWORD" ]]; then | ||
| SSHPASS="$PASSWORD" sshpass -e scp $SSH_OPTS "$src" "${USER}@${HOST}:${dst}" | ||
| fi |
There was a problem hiding this comment.
In scp_cmd, if neither --key nor DEPLOY_PASSWORD is provided, the function becomes a no-op (no scp is executed) and the script still prints "Deployed successfully". This makes failures confusing and can result in later steps failing for unrelated reasons. Mirror ssh_cmd's behavior by adding an explicit else branch that errors out when no auth method is configured (and consider sharing the sshpass presence check here too).
| elif [[ -n "$PASSWORD" ]]; then | ||
| SSHPASS="$PASSWORD" sshpass -e scp $SSH_OPTS "$src" "${USER}@${HOST}:${dst}" | ||
| fi |
There was a problem hiding this comment.
The password-auth branch uses sshpass without checking that it exists (unlike ssh_cmd). With set -e, this will fail as "command not found" without a clear message. Add the same command -v sshpass guard (or refactor so both ssh/scp paths share a single validation).
| for _, p := range parts { | ||
| trimmed := strings.TrimLeft(p, "v") | ||
| if len(trimmed) > 0 && trimmed[0] >= '0' && trimmed[0] <= '9' { | ||
| return p |
There was a problem hiding this comment.
cleanVersionString looks like it intends to normalize versions like "v1.2.3" to "1.2.3" (it computes trimmed := strings.TrimLeft(p, "v") and the doc examples omit the leading v), but it currently returns p rather than trimmed. This means version strings can retain the leading 'v' unexpectedly. Return the trimmed token (or update the comment/examples if keeping the 'v' is intentional).
| return p | |
| return trimmed |
| _, stderr, exitCode, err := exec.Run(ctx, "schtasks", args...) | ||
| if err != nil || exitCode != 0 { | ||
| return fmt.Errorf("failed to create scheduled task: %s", stderr) | ||
| } |
There was a problem hiding this comment.
Install drops useful error context when schtasks fails: it ignores err and exitCode and returns an error built only from stderr (which can be empty, e.g. if the executable is missing or the executor returns a non-zero exit without stderr). Include the exit code and wrap/append the underlying err (and optionally stdout/stderr) so failures are diagnosable.
| _, stderr, exitCode, err := exec.Run(ctx, "schtasks", "/delete", "/tn", taskName, "/f") | ||
| if err != nil || exitCode != 0 { | ||
| return fmt.Errorf("failed to delete scheduled task: %s", stderr) | ||
| } |
There was a problem hiding this comment.
doUninstall has the same issue as Install: it discards err/exitCode and reports only stderr, which can be empty and hides the real cause of failure. Return an error that includes the exit code and wraps the underlying err (and include stderr/stdout for troubleshooting).
| _ = Install(mock, newTestLogger()) | ||
| // If we got past the config parsing without panic, the frequency was handled correctly. | ||
| } |
There was a problem hiding this comment.
TestInstall_CustomFrequency doesn't assert any observable behavior (it ignores Install's return value and only relies on "no panic"), so it will pass even if custom frequency handling regresses. Consider refactoring the frequency parsing/task-argument construction into a helper that can be unit-tested deterministically (or stub the expected schtasks /create invocation and assert the "/mo" value).
What does this PR do?
Adds official windows support.
Type of change
Testing
./stepsecurity-dev-machine-guard --verbose./stepsecurity-dev-machine-guard --json | python3 -m json.toolmake lintmake testRelated Issues