Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions cmd/stepsecurity-dev-machine-guard/main.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"runtime"

"github.com/step-security/dev-machine-guard/internal/buildinfo"
"github.com/step-security/dev-machine-guard/internal/cli"
"github.com/step-security/dev-machine-guard/internal/config"
"github.com/step-security/dev-machine-guard/internal/detector/configaudit"
"github.com/step-security/dev-machine-guard/internal/device"
"github.com/step-security/dev-machine-guard/internal/executor"
"github.com/step-security/dev-machine-guard/internal/launchd"
"github.com/step-security/dev-machine-guard/internal/output"
"github.com/step-security/dev-machine-guard/internal/progress"
"github.com/step-security/dev-machine-guard/internal/scan"
"github.com/step-security/dev-machine-guard/internal/schtasks"
Expand Down Expand Up @@ -160,6 +166,22 @@ func main() {
}

default:
// --npmrc and --pipconfig: focused, verbose pretty audits that
// bypass everything else for a fast (~1s) deep dive.
if cfg.NPMRCOnly {
if err := runNPMRCOnly(exec, cfg); err != nil {
log.Error("%v", err)
os.Exit(1)
}
return
}
if cfg.PipConfigOnly {
if err := runPipConfigOnly(exec, cfg); err != nil {
log.Error("%v", err)
os.Exit(1)
}
return
}
// Community mode or auto-detect enterprise
switch {
case cfg.OutputFormatSet || cfg.HTMLOutputFile != "":
Expand All @@ -184,3 +206,63 @@ func main() {
}
}
}

// runNPMRCOnly executes only the npmrc detector and renders the verbose
// pretty view (or JSON when --json is also passed). Skips IDE / AI / Brew /
// Python / Node / pip detection so the run is fast and the output is
// exclusively about npm configuration.
func runNPMRCOnly(exec executor.Executor, cfg *cli.Config) error {
ctx := context.Background()
dev := device.Gather(ctx, exec)
loggedInUser, _ := exec.LoggedInUser()

searchDirs := resolveScanSearchDirs(exec, cfg.SearchDirs)
audit := configaudit.NewNPMRCDetector(exec).Detect(ctx, searchDirs, loggedInUser)

if cfg.OutputFormat == "json" {
return scanJSONEncoder(os.Stdout).Encode(audit)
}
output.PrettyNPMRC(os.Stdout, &audit, dev, cfg.ColorMode)
return nil
}

// runPipConfigOnly executes only the pip-config detector and renders the
// verbose pretty view (or JSON when --json is also passed).
func runPipConfigOnly(exec executor.Executor, cfg *cli.Config) error {
ctx := context.Background()
dev := device.Gather(ctx, exec)
loggedInUser, _ := exec.LoggedInUser()

audit := configaudit.NewPipConfigDetector(exec).Detect(ctx, loggedInUser)

if cfg.OutputFormat == "json" {
return scanJSONEncoder(os.Stdout).Encode(audit)
}
output.PrettyPipConfig(os.Stdout, &audit, dev, cfg.ColorMode)
return nil
}

// resolveScanSearchDirs expands `$HOME` to the logged-in user's home dir
// and leaves other entries unchanged. Mirrors the helper inside scan.Run
// so --npmrc walks the same project tree the full scan would.
func resolveScanSearchDirs(exec executor.Executor, dirs []string) []string {
resolved := make([]string, 0, len(dirs))
for _, d := range dirs {
if d == "$HOME" {
if u, err := exec.LoggedInUser(); err == nil {
d = u.HomeDir
}
}
resolved = append(resolved, d)
}
return resolved
}

// scanJSONEncoder returns a 2-space-indented JSON encoder that doesn't
// HTML-escape — same conventions as the standard scan output.
func scanJSONEncoder(w io.Writer) *json.Encoder {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
enc.SetEscapeHTML(false)
return enc
}
8 changes: 8 additions & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type Config struct {
EnableBrewScan *bool // nil=auto, true/false=explicit
EnablePythonScan *bool // nil=auto, true/false=explicit
IncludeBundledPlugins bool // --include-bundled-plugins: include bundled/platform plugins in output
NPMRCOnly bool // --npmrc: run only the npmrc audit and render verbose pretty output
PipConfigOnly bool // --pipconfig: run only the pip config audit and render verbose pretty output
SearchDirs []string // defaults to ["$HOME"]
}

Expand Down Expand Up @@ -87,6 +89,10 @@ func Parse(args []string) (*Config, error) {
cfg.EnablePythonScan = &v
case arg == "--include-bundled-plugins":
cfg.IncludeBundledPlugins = true
case arg == "--npmrc":
cfg.NPMRCOnly = true
case arg == "--pipconfig":
cfg.PipConfigOnly = true
case strings.HasPrefix(arg, "--color="):
mode := strings.TrimPrefix(arg, "--color=")
if mode != "auto" && mode != "always" && mode != "never" {
Expand Down Expand Up @@ -163,6 +169,8 @@ Options:
--enable-python-scan Enable Python package scanning
--disable-python-scan Disable Python package scanning
--include-bundled-plugins Include bundled/platform plugins in output (Windows)
--npmrc Run ONLY the npm config audit (verbose pretty view; --json supported)
--pipconfig Run ONLY the pip config audit (verbose pretty view; --json supported)
--log-level=LEVEL Log level: error | warn | info | debug (default: info)
--verbose Shortcut for --log-level=debug
--color=WHEN Color mode: auto | always | never (default: auto)
Expand Down
Loading
Loading