diff --git a/internal/commands/agenthooks/guardrails/kics/kics.go b/internal/commands/agenthooks/guardrails/kics/kics.go index 10048f77..c73740c1 100644 --- a/internal/commands/agenthooks/guardrails/kics/kics.go +++ b/internal/commands/agenthooks/guardrails/kics/kics.go @@ -6,6 +6,7 @@ import ( "strings" agenthooks "github.com/Checkmarx/ast-cx-hooks" + "github.com/checkmarx/ast-cli/internal/logger" "github.com/checkmarx/ast-cli/internal/params" "github.com/checkmarx/ast-cli/internal/services/realtimeengine/ignore" ) @@ -45,6 +46,7 @@ func isSupportedByKICS(filePath string) bool { func ScanFileEdit(ev agenthooks.FileEditEvent, svc *Scanner) (blocked bool, reason, context string) { defer func() { if r := recover(); r != nil { + logger.PrintfIfVerbose("kics guardrail: recovered from panic, failing open: %v", r) blocked = false reason = "" context = "" @@ -70,6 +72,7 @@ func ScanFileEdit(ev agenthooks.FileEditEvent, svc *Scanner) (blocked bool, reas newResults, err := svc.scan(stagedNew) if err != nil { // Fail open: Docker unavailable, image pull failure, feature flag disabled, etc. + logger.PrintfIfVerbose("kics guardrail: scan of proposed content failed, failing open: %v", err) return false, "", "" } if len(newResults) == 0 { @@ -92,6 +95,7 @@ func ScanFileEdit(ev agenthooks.FileEditEvent, svc *Scanner) (blocked bool, reas origResults, err := svc.scan(stagedOrig) if err != nil { // Fail open on original scan error + logger.PrintfIfVerbose("kics guardrail: scan of original content failed, failing open: %v", err) return false, "", "" } diff --git a/internal/commands/agenthooks/guardrails/kics/scanner.go b/internal/commands/agenthooks/guardrails/kics/scanner.go index e1e99a12..c539775c 100644 --- a/internal/commands/agenthooks/guardrails/kics/scanner.go +++ b/internal/commands/agenthooks/guardrails/kics/scanner.go @@ -1,6 +1,10 @@ package kics import ( + "os" + "os/exec" + + "github.com/checkmarx/ast-cli/internal/params" "github.com/checkmarx/ast-cli/internal/services/realtimeengine/iacrealtime" "github.com/checkmarx/ast-cli/internal/wrappers" ) @@ -27,7 +31,33 @@ func NewScannerWithFunc(f func(path string) ([]iacrealtime.IacRealtimeResult, er return &Scanner{scan: f} } +// defaultContainerEngine mirrors the "docker" default of the --engine flag on +// the manual `cx scan iac-realtime` command (internal/commands/scan.go), used +// when neither an override nor auto-detection finds a usable engine. +const defaultContainerEngine = "docker" + +// resolveContainerEngine picks the container engine name to pass to +// RunIacRealtimeScan. The guardrail is invoked as `cx hooks ` with only +// stdin JSON (no --engine flag like the manual `cx scan iac-realtime` +// command), so it resolves the engine itself: +// 1. HooksContainerEngineEnv, if set — lets a Podman/Colima-only user (or the +// agent plugin's own hook environment) override the choice explicitly. +// 2. Auto-detect via PATH lookup: try "docker" then "podman", first one found wins. +// 3. defaultContainerEngine, if neither resolves — preserves prior behavior +// and existing error messaging when no engine is installed at all. +func resolveContainerEngine() string { + if engine := os.Getenv(params.HooksContainerEngineEnv); engine != "" { + return engine + } + for _, engine := range []string{"docker", "podman"} { + if _, err := exec.LookPath(engine); err == nil { + return engine + } + } + return defaultContainerEngine +} + func (s *Scanner) runRealScan(path string) ([]iacrealtime.IacRealtimeResult, error) { svc := iacrealtime.NewIacRealtimeService(s.jwt, s.ff, iacrealtime.NewContainerManager()) - return svc.RunIacRealtimeScan(path, "", existingIgnoreFilePath()) + return svc.RunIacRealtimeScan(path, resolveContainerEngine(), existingIgnoreFilePath()) } diff --git a/internal/commands/agenthooks/guardrails/kics/scanner_test.go b/internal/commands/agenthooks/guardrails/kics/scanner_test.go new file mode 100644 index 00000000..efadcb9a --- /dev/null +++ b/internal/commands/agenthooks/guardrails/kics/scanner_test.go @@ -0,0 +1,53 @@ +//go:build !integration + +package kics + +import ( + "os" + "testing" + + "github.com/checkmarx/ast-cli/internal/params" +) + +// ── resolveContainerEngine ─────────────────────────────────────────────────── + +func TestResolveContainerEngine_EnvOverrideWins(t *testing.T) { + t.Setenv(params.HooksContainerEngineEnv, "podman") + if got := resolveContainerEngine(); got != "podman" { + t.Errorf("expected env override %q, got %q", "podman", got) + } +} + +func TestResolveContainerEngine_EnvOverrideArbitraryValue(t *testing.T) { + t.Setenv(params.HooksContainerEngineEnv, "nerdctl") + if got := resolveContainerEngine(); got != "nerdctl" { + t.Errorf("expected env override %q, got %q", "nerdctl", got) + } +} + +func TestResolveContainerEngine_FallsBackToDefaultWhenNothingResolves(t *testing.T) { + t.Setenv(params.HooksContainerEngineEnv, "") + // Point PATH somewhere with no docker/podman binaries so auto-detection + // finds nothing and falls back to the default. + emptyDir := t.TempDir() + t.Setenv("PATH", emptyDir) + + if got := resolveContainerEngine(); got != defaultContainerEngine { + t.Errorf("expected fallback default %q, got %q", defaultContainerEngine, got) + } +} + +func TestResolveContainerEngine_AutoDetectsFromPath(t *testing.T) { + t.Setenv(params.HooksContainerEngineEnv, "") + + dir := t.TempDir() + podmanPath := dir + string(os.PathSeparator) + "podman" + if err := os.WriteFile(podmanPath, []byte("#!/bin/sh\n"), 0o700); err != nil { + t.Fatalf("failed to create fake podman binary: %v", err) + } + t.Setenv("PATH", dir) + + if got := resolveContainerEngine(); got != "podman" { + t.Errorf("expected auto-detected %q, got %q", "podman", got) + } +} diff --git a/internal/params/envs.go b/internal/params/envs.go index 44134a69..42982dd7 100644 --- a/internal/params/envs.go +++ b/internal/params/envs.go @@ -24,6 +24,7 @@ const ( CodeBashingPathEnv = "CX_CODEBASHING_PATH" GroupsPathEnv = "CX_GROUPS_PATH" AgentNameEnv = "CX_AGENT_NAME" + HooksContainerEngineEnv = "CX_HOOKS_CONTAINER_ENGINE" OriginEnv = "CX_ORIGIN" ProjectsPathEnv = "CX_PROJECTS_PATH" ApplicationsPathEnv = "CX_APPLICATIONS_PATH"