feat: add linux support#36
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>
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
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 Linux support across scanning, output, and installation workflows, while also refactoring some Windows-specific implementations to use native APIs instead of shelling out.
Changes:
- Introduce platform constants and shared platform display naming.
- Add Linux scanning coverage (system package managers + Linux IDE path detection) and surface results in pretty/HTML outputs.
- Add Linux installer/uninstaller via systemd user timer and improve Windows implementations using
golang.org/x/sys/windows.
Reviewed changes
Copilot reviewed 35 out of 37 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/telemetry/telemetry.go | Uses platform constants for Windows-only filtering logic. |
| internal/systemd/systemd.go | Adds systemd user service/timer installation for Linux. |
| internal/scan/scanner.go | Adds Linux system package scanning and includes results in ScanResult. |
| internal/output/pretty_test.go | Adds coverage for platform label display in pretty output. |
| internal/output/pretty.go | Uses platform display name helper; prints Linux system/snap/flatpak package sections. |
| internal/output/html_test.go | Adds coverage for platform label display in HTML output. |
| internal/output/html.go | Uses platform display name helper in HTML template rendering. |
| internal/model/platform.go | Adds platform constants and PlatformDisplayName. |
| internal/model/model.go | Extends ScanResult/Summary with Linux package manager + package lists. |
| internal/lock/lock_windows.go | Switches Windows PID liveness check to native OpenProcess. |
| internal/executor/executor_windows.go | Implements elevated check via Windows token API. |
| internal/device/device_windows.go | Adds native Windows serial/OS version collection via registry + RtlGetVersion. |
| internal/device/device_test.go | Adds Linux device info unit tests (including fallbacks). |
| internal/device/device_other.go | Provides non-Windows stubs for Windows-only helpers to support mock tests. |
| internal/device/device.go | Adds Linux serial + OS version collection and uses platform constants. |
| internal/detector/xcode_extensions.go | Prevents macOS-only detector from running on non-darwin platforms. |
| internal/detector/syspkg.go | Adds Linux system package manager + snap/flatpak detection and parsing. |
| internal/detector/shellcmd.go | Uses platform constants for quoting behavior. |
| internal/detector/registry_windows.go | Adds native Windows registry implementation for version/install location lookup. |
| internal/detector/registry_other.go | Adds non-Windows stub for registry lookup to support mock tests. |
| internal/detector/process_windows.go | Adds native Windows process enumeration implementation. |
| internal/detector/process_other.go | Adds non-Windows stub for Windows process matching to support mock tests. |
| internal/detector/process.go | Adds Linux /proc-based process matching and routes by platform constants. |
| internal/detector/nodescan.go | Uses platform constants for RunAsUser gating. |
| internal/detector/mcp.go | Adds Linux config-path variants for MCP config discovery. |
| internal/detector/jetbrains_plugins.go | Makes zip close defers ignore close errors consistently. |
| internal/detector/jetbrains.go | Adds Linux JetBrains config dir resolution and product-info path logic. |
| internal/detector/ide.go | Adds Linux IDE detection (paths, PATH, .desktop fallback) + moves registry logic behind build tags. |
| internal/detector/framework.go | Adds Linux detection path for LM Studio. |
| internal/detector/eclipse_plugins_test.go | Refactors test counting logic (switch). |
| internal/detector/eclipse_plugins.go | Uses platform constants for Windows-only code path selection. |
| internal/detector/aicli.go | Uses platform constants for Windows .exe handling. |
| internal/detector/agent.go | Explicitly handles Linux unsupported Claude Desktop case. |
| go.sum | Adds golang.org/x/sys checksums. |
| go.mod | Adds dependency on golang.org/x/sys. |
| cmd/stepsecurity-dev-machine-guard/main.go | Uses systemd install/uninstall on non-Windows/darwin platforms. |
| .gitignore | Ignores Linux binary artifact name. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [Service] | ||
| Type=oneshot | ||
| ExecStart={{.BinaryPath}} send-telemetry | ||
| StandardOutput=append:{{.LogDir}}/agent.log |
There was a problem hiding this comment.
ExecStart is rendered without any quoting/escaping. If the installed binary path contains spaces or characters that need escaping for systemd unit parsing, the service will fail to start. Consider using systemd-compatible escaping (or a helper/template func) so the path is safe in ExecStart.
| h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid)) | ||
| if err != nil { | ||
| return false | ||
| } | ||
| return !strings.Contains(string(output), "No tasks") | ||
| _ = windows.CloseHandle(h) |
There was a problem hiding this comment.
windows.OpenProcess can fail with access denied for processes owned by another user/session even when the PID is alive. Returning false in that case can break the lock (allowing multiple instances). Consider treating access-denied as “alive” (or using a different liveness check).
| stdout, _, _, err := d.exec.RunWithTimeout(ctx, 30*time.Second, | ||
| "flatpak", "list", "--app", "--columns=application,version") | ||
| if err != nil { |
There was a problem hiding this comment.
ListFlatpakPackages only checks err, but Executor returns err == nil for non-zero exit codes. Please also check exitCode before parsing stdout so a failing flatpak list doesn’t get treated as valid/empty output.
| stdout, _, _, err := d.exec.RunWithTimeout(ctx, 30*time.Second, | |
| "flatpak", "list", "--app", "--columns=application,version") | |
| if err != nil { | |
| stdout, _, exitCode, err := d.exec.RunWithTimeout(ctx, 30*time.Second, | |
| "flatpak", "list", "--app", "--columns=application,version") | |
| if err != nil || exitCode != 0 { |
| // Detect finds the active system package manager and returns its info. | ||
| // Returns nil on non-Linux platforms or if no known PM is found. | ||
| func (d *SystemPkgDetector) Detect(ctx context.Context) *model.PkgManager { | ||
| if d.exec.GOOS() != model.PlatformLinux { | ||
| return nil |
There was a problem hiding this comment.
System package detection/parsing is newly added here but doesn’t appear to have mock-based unit tests (other detectors in this package do). Adding tests for rpm/dpkg/pacman/apk outputs and snap/flatpak parsing would help prevent regressions across distros.
| if base == "bin" { | ||
| return filepath.Dir(dir) | ||
| } | ||
| return "unknown" | ||
| // /usr/share/cursor/resources/app/bin/cursor -> /usr/share/cursor | ||
| if base == "bin" || filepath.Base(filepath.Dir(dir)) == "app" { |
There was a problem hiding this comment.
resolveInstallDirFromBinary returns early whenever base == "bin". That means paths like "/usr/share/cursor/resources/app/bin/cursor" will resolve to "/usr/share/cursor/resources/app" and never reach the intended resources/app/bin stripping logic below. Please fix the control flow so resources/app/bin is handled correctly.
| stdout, _, _, err := d.exec.RunWithTimeout(ctx, 60*time.Second, spec.Binary, spec.ListCmd...) | ||
| if err != nil { |
There was a problem hiding this comment.
Executor.RunWithTimeout/Run return err == nil for non-zero exit codes. ListPackages only checks err, so failures can be treated as success and stdout parsed incorrectly. Please also check exitCode == 0 before parsing stdout.
| stdout, _, _, err := d.exec.RunWithTimeout(ctx, 60*time.Second, spec.Binary, spec.ListCmd...) | |
| if err != nil { | |
| stdout, _, exitCode, err := d.exec.RunWithTimeout(ctx, 60*time.Second, spec.Binary, spec.ListCmd...) | |
| if err != nil || exitCode != 0 { |
| stdout, _, _, err := d.exec.RunWithTimeout(ctx, 30*time.Second, "snap", "list") | ||
| if err != nil { |
There was a problem hiding this comment.
ListSnapPackages only checks err, but Executor returns err == nil for non-zero exit codes. Please also check exitCode before parsing stdout so a failing snap list doesn’t get treated as valid/empty output.
| stdout, _, _, err := d.exec.RunWithTimeout(ctx, 30*time.Second, "snap", "list") | |
| if err != nil { | |
| stdout, _, exitCode, err := d.exec.RunWithTimeout(ctx, 30*time.Second, "snap", "list") | |
| if err != nil || exitCode != 0 { |
| default: | ||
| if err := systemd.Install(exec, log); err != nil { | ||
| log.Error("%v", err) | ||
| os.Exit(1) | ||
| } |
There was a problem hiding this comment.
The default branch treats any non-Windows/non-darwin platform as Linux and attempts a systemd install. That will break on unsupported Unix targets (e.g., freebsd). Please gate systemd to runtime.GOOS == "linux" and return a clear unsupported-platform error otherwise.
| default: | ||
| if err := systemd.Uninstall(exec, log); err != nil { | ||
| log.Error("%v", err) | ||
| os.Exit(1) | ||
| } |
There was a problem hiding this comment.
The default branch will run systemd uninstall on any non-Windows/non-darwin OS. Please restrict this to runtime.GOOS == "linux" and return a clear unsupported-platform error otherwise.
| if _, _, _, err := exec.Run(ctx, "systemctl", "--user", "daemon-reload"); err != nil { | ||
| return fmt.Errorf("daemon-reload failed: %w", err) | ||
| } |
There was a problem hiding this comment.
exec.Run returns err == nil even when the command exits non-zero (it surfaces the exit code separately). Here daemon-reload only checks err, so failures with a non-zero exit code will be treated as success. Please check exitCode and include stderr in the returned error (similar to the enable --now handling below).
- Gate systemd install/uninstall to runtime.GOOS == "linux" with unsupported-platform error for other Unix targets - Fix lock_windows.go: treat ERROR_ACCESS_DENIED as process alive - Fix systemd.go: check daemon-reload exit code, escape paths with spaces in unit files - Fix syspkg.go: check exit codes on all RunWithTimeout calls - Fix ide.go resolveInstallDirFromBinary: correct control flow for resources/app/bin layout - Fix ide.go parseDesktopExec: handle wrapper commands (env VAR=...) by scanning for first absolute path - Fix ide.go resolveLinuxVersion: prefer binary inside installDir over PATH to avoid version mismatch
27b4082 to
8055c59
Compare
What does this PR do?
Type of change
Testing
./stepsecurity-dev-machine-guard --verbose./stepsecurity-dev-machine-guard --json | python3 -m json.toolmake lintmake testRelated Issues