Skip to content
Merged
8 changes: 8 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,11 @@ GHSA-vh4v-2xq2-g5cg exp:2026-12-31
# Risk: Low - requires attacker-controlled registry, container scanning only
# Impact: Credential exfiltration in container registry operations
CVE-2026-48978 exp:2026-12-31

# CVE-2026-6791 (UNKNOWN): glibc vulnerability
# Libraries: glibc, glibc-locale-posix, ld-linux, libcrypt1
# Image: wolfi 20230201 (base image)
# Status: Fixed in glibc 2.43-r10 (current: 2.43-r8)
# Risk: Low - affects base OS image, not application code
# Impact: Minimal - only affects base OS components, application uses glibc runtime only
CVE-2026-6791 exp:2026-12-31
4 changes: 2 additions & 2 deletions internal/commands/agenthooks/guardrails/asca/delta.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// findingKey is the deduplication tuple used for delta detection.
// Mirrors the cx-security plugin's matching logic.
// Mirrors the cx-devassist plugin's matching logic.
type findingKey struct {
ruleID uint32
problematicLine string // TrimSpace applied
Expand Down Expand Up @@ -115,7 +115,7 @@ func additionalContext(filePath, cxBinary string, findings []grpcs.ScanDetail, w
"Do not bypass the scan by writing the same content through another tool or shell command. "+
"ANALYZE each finding to determine if it is a real vulnerability or a false positive "+
"caused by ASCA's single-file scope (it cannot see imported modules or helper files). "+
"For each real finding, invoke the cx-security:cx-security-asca skill — "+
"For each real finding, invoke the cx-devassist:cx-devassist-asca skill — "+
"the findings are already in context so it will skip the scan and go directly to "+
"MCP-driven remediation; the skill also handles MCP unavailability and self-recovery. "+
"If that skill is not available in this session, call mcp__Checkmarx__codeRemediation directly:\n"+
Expand Down
54 changes: 54 additions & 0 deletions internal/services/realtimeengine/ossrealtime/oss-realtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/Checkmarx/manifest-parser/pkg/parser"
Expand Down Expand Up @@ -74,6 +75,10 @@ func (o *OssRealtimeService) RunOssRealtimeScan(filePath, ignoredFilePath string
return nil, errorconstants.NewRealtimeEngineError("invalid file path").Error()
}

if err := validateSupportedManifestFile(filePath); err != nil {
return nil, err
}

pkgs, err := parseManifest(filePath)
if err != nil {
logger.PrintfIfVerbose("Failed to parse manifest file %s: %v", filePath, err)
Expand Down Expand Up @@ -174,6 +179,55 @@ func getPackageEntryFromPackageMap(
return &entry
}

// validateSupportedManifestFile checks if the manifest file format is supported by OSS realtime scanner.
func validateSupportedManifestFile(filePath string) error {
manifestFileName := filepath.Base(filePath)
manifestFileExtension := filepath.Ext(manifestFileName)

// Check supported extensions
supportedExtensions := map[string]bool{
".csproj": true,
".sbt": true,
}

// Check supported filenames
supportedFilenames := map[string]bool{
"pom.xml": true,
"package.json": true,
"Directory.Packages.props": true,
"packages.config": true,
"go.mod": true,
"build.gradle": true,
"build.gradle.kts": true,
"libs.versions.toml": true,
"setup.cfg": true,
"setup.py": true,
"pyproject.toml": true,
}

// Check by extension
if supportedExtensions[manifestFileExtension] {
return nil
}

// Check by filename
if supportedFilenames[manifestFileName] {
return nil
}

// Special handling for .txt files (check prefix)
if manifestFileExtension == ".txt" {
if strings.HasPrefix(manifestFileName, "requirement") ||
strings.HasPrefix(manifestFileName, "packages") ||
strings.HasPrefix(manifestFileName, "constraint") {
return nil
}
}

// Manifest format is not supported
return errorconstants.NewRealtimeEngineError(fmt.Sprintf("OSS Realtime scanner doesn't currently support scanning '%s' file.", manifestFileName)).Error()
}

// parseManifest parses the manifest file and returns a list of packages.
func parseManifest(filePath string) ([]models.Package, error) {
manifestParser := parser.ParsersFactory(filePath)
Expand Down
9 changes: 8 additions & 1 deletion internal/wrappers/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type ClientCredentialsError struct {
const FailedToAuth = "Failed to authenticate - please provide an %s"
const BaseAuthURLSuffix = "protocol/openid-connect/token"
const BaseAuthURLPrefix = "auth/realms/organization"

// BaseURLKey is the JWT claim that carries the AST base URL. It is present only
// on the exchanged access token (see GetURL), not on the stored refresh token.
const BaseURLKey = "ast-base-url"
Expand Down Expand Up @@ -129,7 +130,13 @@ func retryHTTPForIAMRequest(requestFunc func() (*http.Response, error), retries
}

func setAgentNameAndOrigin(req *http.Request, isAuth bool) {
agentStr := viper.GetString(commonParams.AgentNameKey) + "/" + commonParams.Version

var agentStr string
if strings.Contains(viper.GetString(commonParams.AgentNameKey), "_") {
agentStr = viper.GetString(commonParams.AgentNameKey) + "/ASTCLI_" + commonParams.Version
} else {
agentStr = viper.GetString(commonParams.AgentNameKey) + "/" + commonParams.Version
}
req.Header.Set("User-Agent", agentStr)

originStr := viper.GetString(commonParams.OriginKey)
Expand Down
77 changes: 77 additions & 0 deletions test/integration/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2746,3 +2746,80 @@ func TestCreateScan_AsMultipartUpload_Success(t *testing.T) {
log.SetOutput(os.Stderr)
assert.Assert(t, strings.Contains(buf.String(), "Uploading source code in multiple parts"), "Test for uploading file in multiple parts failed.")
}

// Scenario 1: Test scan creation with a project already associated with an application
// Pre-condition: Project and application exist and are already linked
// Verifies the CLI skips reassociation and logs the expected message during scan creation.
func TestScanCreate_ProjectAlreadyAssociatedWithApplication_SkipsAssociation(t *testing.T) {
// Capture debug output to verify log messages
var buf bytes.Buffer
log.SetOutput(&buf)
defer log.SetOutput(os.Stderr)

args := []string{
"scan", "create",
flag(params.ProjectName), "TestProjectAppAssign", // Pre-existing project
flag(params.ApplicationName), "TestApplicationProAssign", // Pre-existing, already associated application
flag(params.SourcesFlag), "data/sources-gitignore.zip",
flag(params.ScanTypes), params.IacType,
flag(params.BranchFlag), "main",
flag(params.DebugFlag),
}

err, _ := executeCommand(t, args...)
assert.NilError(t, err, "Scan creation with already-associated project and application should succeed")

logOutput := buf.String()
assert.Assert(t, strings.Contains(logOutput, "Project is already associated with the application. Skipping association"),
"Expected log message about skipping association not found. Log output: %s", logOutput)
}

// Scenario 2: Create a scan with a new project and an existing application
// Pre-condition: Application already exists
// Verifies the project is created, linked to the application, and a success message is logged.
func TestScanCreate_NewProjectWithExistingApplication_SuccessfullyUpdatesApplication(t *testing.T) {
// Capture debug output to verify log messages
var buf bytes.Buffer
log.SetOutput(&buf)
defer log.SetOutput(os.Stderr)

_, projectName := createNewProject(t, nil, nil, GenerateRandomProjectNameForScan())
defer deleteProjectByName(t, projectName)

args := []string{
"scan", "create",
flag(params.ProjectName), projectName, // NEW project (created at runtime)
flag(params.ApplicationName), "TestApplicationForProject", // PRE-EXISTING application
flag(params.SourcesFlag), "data/sources-gitignore.zip",
flag(params.ScanTypes), params.IacType,
flag(params.BranchFlag), "main",
flag(params.DebugFlag),
flag(params.ScanInfoFormatFlag), printer.FormatJSON,
}

err, _ := executeCommand(t, args...)
assert.NilError(t, err, "Scan creation with new project and existing application should succeed")

logOutput := buf.String()
assert.Assert(t, strings.Contains(logOutput, "Successfully updated the application"),
"Expected log message about successful application update not found. Log output: %s", logOutput)
}

// Scenario 3: Test scan creation using manually provided existing project and application with --branch-primary and --project-tags.
// Verifies that project parameters are updated successfully (when application-level query override is performed) and the scan completes without errors.
// this test only validates successful execution, not specific log messages.
func TestScanCreate_ProjectAlreadyAssociatedWithApplication_BranchPrimaryQueryOverride(t *testing.T) {
args := []string{
"scan", "create",
flag(params.ProjectName), "TestProjectQueryOverride", // Pre-existing, manually provided project
flag(params.ApplicationName), "TestApplicationQueryOverride", // Pre-existing, manually provided application
flag(params.SourcesFlag), "data/sources-gitignore.zip",
flag(params.ScanTypes), "sast,iac-security", // Scan types as comma-separated value
flag(params.BranchFlag), "main",
flag(params.BranchPrimaryFlag), // Branch primary flag
flag(params.ScanInfoFormatFlag), printer.FormatJSON,
}

err, _ := executeCommand(t, args...)
assert.NilError(t, err, "Scan creation with branch-primary and query override flags should succeed without error")
}
Loading