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
4 changes: 4 additions & 0 deletions internal/commands/agenthooks/guardrails/kics/kics.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 = ""
Expand All @@ -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 {
Expand All @@ -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, "", ""
}

Expand Down
32 changes: 31 additions & 1 deletion internal/commands/agenthooks/guardrails/kics/scanner.go
Original file line number Diff line number Diff line change
@@ -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"
)
Expand All @@ -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 <route>` 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())
}
53 changes: 53 additions & 0 deletions internal/commands/agenthooks/guardrails/kics/scanner_test.go
Original file line number Diff line number Diff line change
@@ -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" {

Check failure on line 16 in internal/commands/agenthooks/guardrails/kics/scanner_test.go

View workflow job for this annotation

GitHub Actions / Lint (golangci-lint)

string `podman` has 2 occurrences, make it a constant (goconst)
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"

Check failure on line 44 in internal/commands/agenthooks/guardrails/kics/scanner_test.go

View workflow job for this annotation

GitHub Actions / Lint (golangci-lint)

preferFilepathJoin: filepath.Join(dir, "podman") should be preferred to the dir + string(os.PathSeparator) + "podman" (gocritic)
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)
}
}
1 change: 1 addition & 0 deletions internal/params/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading