Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cf88a5f
AST-164154: Fix KICS agent-hook guardrail never scanning (empty conta…
cx-anurag-dalke Aug 5, 2026
c9b4b6f
cursor changes
cx-atish-jadhav Aug 6, 2026
3cac686
cursor changes
cx-atish-jadhav Aug 6, 2026
eaf9b77
AST-160114 cursor changes
cx-kedar-bhujade Aug 6, 2026
3321339
Merge feature/cursor-cli-plugin with remote changes - resolve conflicts
cx-atish-jadhav Aug 6, 2026
fc300d1
resolving conflicts
cx-atish-jadhav Aug 6, 2026
70bb966
WIP: cursor plugin changes before merging base branch conflicts
cx-kedar-bhujade Aug 13, 2026
c715d92
Merge remote-tracking branch 'origin/other/release_2_3_60' into featu…
cx-kedar-bhujade Aug 13, 2026
acf49e8
Merge remote-tracking branch 'origin/feature/cursor-cli-plugin' into …
cx-kedar-bhujade Aug 13, 2026
0c36077
Fixed Cursor CLI bugs
cx-kedar-bhujade Aug 13, 2026
dc35d64
Add all package manager support to OSS realtime(AST-146208) (#1539)
cx-atish-jadhav Aug 13, 2026
05a480f
Add comprehensive unit tests (AST-165778) (#1537)
cx-atish-jadhav Aug 13, 2026
59df695
Merge branch 'other/release_2_3_60' into feature/cursor-cli-plugin
cx-kedar-bhujade Aug 13, 2026
c04e048
Add archive checksum verification for Vorpal and SCA Resolver(AST-167…
cx-sumit-morchhale Aug 13, 2026
27d93f4
Merge branch 'other/release_2_3_60' into feature/cursor-cli-plugin
cx-kedar-bhujade Aug 13, 2026
dc0164b
Fix gofmt/goimports lint failures in test files
cx-sumit-morchhale Aug 13, 2026
db4fa16
- Adding additional test coverage
cx-anjali-deore Aug 14, 2026
57abc90
- Adding additional test coverage
cx-anjali-deore Aug 14, 2026
b704355
Merge branch 'other/release_2_3_60' into feature/cursor-cli-plugin
cx-kedar-bhujade Aug 19, 2026
c2b8e48
Merge branch 'main' into feature/cursor-cli-plugin
cx-kedar-bhujade Aug 19, 2026
f06bfaf
Fix kics.NewScannerWithFunc mock signature in tests (AST-160114)
cx-kedar-bhujade Aug 19, 2026
6e1f443
Fix lint findings and raise unit-test coverage (AST-160114)
cx-kedar-bhujade Aug 19, 2026
4264e97
Fix goconst lint finding in content_extra_test.go (AST-160114)
cx-kedar-bhujade Aug 19, 2026
4730c94
Update code owner(AST-0000) (#1542)
cx-sumit-morchhale Aug 19, 2026
f0e5208
Update CODEOWNERS to specify new maintainers
cx-sumit-morchhale Aug 19, 2026
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
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# Each line is a file pattern followed by one or more owners

# Specify the default owners for the entire repository
* @cx-anurag-dalke @cx-anjali-deore @cx-umesh-waghode
* @Checkmarx/cx-maintainers
18 changes: 9 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module github.com/checkmarx/ast-cli

go 1.26.5
go 1.26.6

require (
github.com/Checkmarx/ast-cx-hooks v1.0.5
github.com/Checkmarx/ast-cx-hooks v1.0.6
github.com/Checkmarx/containers-resolver v1.0.34
github.com/Checkmarx/containers-types v1.0.9
github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63
Expand All @@ -29,9 +29,9 @@ require (
github.com/stretchr/testify v1.11.1
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.53.0
golang.org/x/crypto v0.55.0
golang.org/x/sync v0.22.0
golang.org/x/text v0.39.0
golang.org/x/text v0.41.0
google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
gopkg.in/yaml.v3 v3.0.1
Expand Down Expand Up @@ -292,13 +292,13 @@ require (
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/mod v0.40.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.47.0 // indirect
golang.org/x/tools v0.49.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
Expand Down
32 changes: 16 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Checkmarx/ast-cx-hooks v1.0.5 h1:4Og5JeBBg3SynAErAP76oGKrjoWrlduWRgg1V9IXjWo=
github.com/Checkmarx/ast-cx-hooks v1.0.5/go.mod h1:GPHk8IJHQlCW7l8ye9/Bij57zYQGRG+pxJPiGgsR8cY=
github.com/Checkmarx/ast-cx-hooks v1.0.6 h1:8/Kcl9V0XKeY1vgTKJR6eIfXXoa4c9DgUOBuY1Ms268=
github.com/Checkmarx/ast-cx-hooks v1.0.6/go.mod h1:GPHk8IJHQlCW7l8ye9/Bij57zYQGRG+pxJPiGgsR8cY=
github.com/Checkmarx/containers-images-extractor v1.0.22 h1:kJZgwk28LwJZ7Xky+kzwL+JSZOlpwrGsZQhhz4L2t6s=
github.com/Checkmarx/containers-images-extractor v1.0.22/go.mod h1:HyzVb8TtTDf56hGlSakalPXtzjJ6VhTYe9fmAcOS+V8=
github.com/Checkmarx/containers-resolver v1.0.34 h1:KULN8s8xb1tQtdH4yzHVdwN8GyLqtPCAkFWra10k7V0=
Expand Down Expand Up @@ -1112,8 +1112,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -1153,8 +1153,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -1200,8 +1200,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -1311,13 +1311,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand All @@ -1328,8 +1328,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down Expand Up @@ -1390,8 +1390,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
46 changes: 46 additions & 0 deletions internal/commands/agenthooks/cursorplugin/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Package cursorplugin holds Cursor-plugin-specific fragments for agent-hook remediation
// guidance (MCP tool names, PowerShell stop-parsing suppress commands).
package cursorplugin

import (
"fmt"
"runtime"
"strings"
)

// MCPServerID is how Cursor names the Checkmarx MCP when cx-devassist is installed as a plugin
// (plugin id "cx-devassist" + mcp.json server key "Checkmarx").
const MCPServerID = "plugin-cx-devassist-Checkmarx"

// MCPTool returns the fully-qualified Cursor MCP tool name for a remediation tool.
func MCPTool(tool string) string {
return "mcp__" + MCPServerID + "__" + tool
}

const goosWindows = "windows"

// IgnoreVulnerabilityCommand renders `cx ignore-vulnerability` for Cursor agents.
// On Windows, uses PowerShell --% with the JSON wrapped in double quotes and inner quotes
// backslash-escaped — the only form that survives PowerShell's native argv parsing.
func IgnoreVulnerabilityCommand(cxBinary, scanType string, data []byte, ignoreFlag, provenance string) string {
if runtime.GOOS == goosWindows {
escaped := escapeJSONForStopParsing(string(data))
// escaped is already quote-escaped for PowerShell's double-quoted string rules;
// %q would re-escape it using Go's own rules (e.g. doubling backslashes) and corrupt it.
return fmt.Sprintf(` & %q --%% ignore-vulnerability --scan-type %s --data "%s"%s%s`, //nolint:gocritic
cxBinary, scanType, escaped, ignoreFlag, provenance)
}
escaped := escapeJSONForPOSIX(string(data))
// escaped is already quote-escaped for the POSIX shell's double-quoted string rules;
// %q would re-escape it using Go's own rules (e.g. doubling backslashes) and corrupt it.
return fmt.Sprintf(` %s ignore-vulnerability --scan-type %s --data "%s"%s%s`, //nolint:gocritic
cxBinary, scanType, escaped, ignoreFlag, provenance)
}

func escapeJSONForStopParsing(data string) string {
return strings.ReplaceAll(data, `"`, `\"`)
}

func escapeJSONForPOSIX(data string) string {
return strings.ReplaceAll(data, `"`, `\"`)
}
57 changes: 57 additions & 0 deletions internal/commands/agenthooks/cursorplugin/plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cursorplugin

import (
"runtime"
"strings"
"testing"
)

func TestMCPTool(t *testing.T) {
got := MCPTool("codeRemediation")
want := "mcp__plugin-cx-devassist-Checkmarx__codeRemediation"
if got != want {
t.Errorf("MCPTool() = %q, want %q", got, want)
}
}

func TestIgnoreVulnerabilityCommand_WindowsUsesStopParsing(t *testing.T) {
if runtime.GOOS != goosWindows {
t.Skip("windows-only")
}
data := []byte(`{"FileName":"Demo.java","Line":5,"RuleID":1027}`)
cmd := IgnoreVulnerabilityCommand(`C:\cx\cx.exe`, "asca", data, ` --ignored-file-path "c:/proj/.checkmarx/ignored.json"`, "")
if !strings.Contains(cmd, `--% ignore-vulnerability`) {
t.Errorf("expected --%% stop-parsing, got %q", cmd)
}
want := `--data "{\"FileName\":\"Demo.java\",\"Line\":5,\"RuleID\":1027}"`
if !strings.Contains(cmd, want) {
t.Errorf("expected quoted backslash-escaped JSON, got %q", cmd)
}
}

func TestIgnoreVulnerabilityCommand_UnixEscapesJSON(t *testing.T) {
if runtime.GOOS == goosWindows {
t.Skip("unix-only")
}
data := []byte(`{"FileName":"Demo.java"}`)
cmd := IgnoreVulnerabilityCommand("cx", "asca", data, "", "")
if !strings.Contains(cmd, `\"FileName\"`) {
t.Errorf("expected backslash-escaped JSON on unix, got %q", cmd)
}
}

func TestEscapeJSONForPOSIX_EscapesEmbeddedQuotes(t *testing.T) {
got := escapeJSONForPOSIX(`{"FileName":"Demo.java"}`)
want := `{\"FileName\":\"Demo.java\"}`
if got != want {
t.Errorf("escapeJSONForPOSIX() = %q, want %q", got, want)
}
}

func TestEscapeJSONForPOSIX_NoQuotesUnchanged(t *testing.T) {
got := escapeJSONForPOSIX("no quotes here")
want := "no quotes here"
if got != want {
t.Errorf("escapeJSONForPOSIX() = %q, want %q", got, want)
}
}
2 changes: 1 addition & 1 deletion internal/commands/agenthooks/cx/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func TestCxBeforeFileEdit_TotalFileSize_Rejects(t *testing.T) {

func TestCxBeforeFileEdit_KICSFinding_RejectsWithContext(t *testing.T) {
resetHookGlobals(t)
kicsScanner = kics.NewScannerWithFunc(func(string) ([]iacrealtime.IacRealtimeResult, error) {
kicsScanner = kics.NewScannerWithFunc(func(string, string) ([]iacrealtime.IacRealtimeResult, error) {
return []iacrealtime.IacRealtimeResult{{
Title: "Privileged Container",
SimilarityID: "sim123",
Expand Down
3 changes: 2 additions & 1 deletion internal/commands/agenthooks/cx/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ var Agents = []Agent{
{"cursor-stop", "Cursor agent finished"},
{"cursor-before-shell", "Gate Cursor shell execution"},
{"cursor-before-mcp", "Gate Cursor MCP execution"},
{"cursor-before-file-write", "Gate Cursor file write (preToolUse)"},
{"cursor-before-file-read", "Gate Cursor file read"},
{"cursor-after-file-edit", "React to Cursor file edit"},
{"cursor-after-file-edit", "React to Cursor file edit (postToolUse)"},
{"cursor-before-submit-prompt", "Gate Cursor prompt"},
},
},
Expand Down
61 changes: 60 additions & 1 deletion internal/commands/agenthooks/guardrails/asca/asca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func TestStageForScan_DotDotOriginalPath_ReturnsError(t *testing.T) {
}

func TestStageForScan_FileMode(t *testing.T) {
if runtime.GOOS == "windows" {
if runtime.GOOS == goosWindows {
t.Skip("Unix permission bits (0600) are not enforced on Windows; validated on Linux/macOS CI")
}
staged, cleanup, err := stageForScan("/tmp/secret.py", "secret", "s1", agenthooks.AgentID("test"))
Expand Down Expand Up @@ -340,6 +340,65 @@ func TestAdditionalContext_EmptyFindings_StillContainsRemediationInstruction(t *
}
}

func TestCursorAdditionalContext_UsesPluginMCPTool(t *testing.T) {
ctx := cursorAdditionalContext("main.py", "cx", nil, "", "")
if !strings.Contains(ctx, "mcp__plugin-cx-devassist-Checkmarx__codeRemediation") {
t.Errorf("expected plugin-prefixed MCP tool, got %q", ctx)
}
}

func TestCursorAdditionalContext_CursorSuppressCommandUsesStopParsingOnWindows(t *testing.T) {
findings := []grpcs.ScanDetail{{FileName: "Demo.java", Line: 5, RuleID: 1027}}
ctx := cursorAdditionalContext("Demo.java", "cx", findings, "", "sess-1")
if runtime.GOOS == goosWindows {
if !strings.Contains(ctx, `--% ignore-vulnerability`) {
t.Errorf("expected PowerShell stop-parsing on windows, got %q", ctx)
}
if strings.Contains(ctx, `""FileName""`) {
t.Errorf("must not use doubled-quote escaping, got %q", ctx)
}
if !strings.Contains(ctx, `\"FileName\"`) {
t.Errorf("expected backslash-escaped JSON in stop-parsing form, got %q", ctx)
}
}
}

func TestCursorEscapeJSON_MatchesTheShellCursorActuallyRunsOn(t *testing.T) {
got := cursorEscapeJSON(`{"FileName":"Demo.java"}`)
if runtime.GOOS == goosWindows {
// PowerShell double-quoted strings escape an embedded `"` by doubling it; a
// backslash is not a quote-escape there, so `\"` would corrupt the command.
want := `{""FileName"":""Demo.java""}`
if got != want {
t.Errorf("expected doubled-quote escaping on windows (PowerShell), got %q", got)
}
} else {
want := `{\"FileName\":\"Demo.java\"}`
if got != want {
t.Errorf("expected backslash-escaped quotes on unix (bash), got %q", got)
}
}
}

func TestFormatFindings_RoutesCursorQuoting(t *testing.T) {
findings := []grpcs.ScanDetail{{FileName: "a.py", Line: 1, RuleID: 1}}
_, ctx := formatFindings("a.py", findings, "", "Cursor", "sess-1")
if runtime.GOOS == goosWindows {
if !strings.Contains(ctx, `--% ignore-vulnerability`) {
t.Fatalf("cursor agent on windows should get stop-parsing suppress command, got %q", ctx)
}
} else if !strings.Contains(ctx, `ignore-vulnerability --scan-type asca --data "`) {
t.Fatalf("cursor agent on unix should get double-quoted suppress command, got %q", ctx)
}
if !strings.Contains(ctx, "mcp__plugin-cx-devassist-Checkmarx__codeRemediation") {
t.Fatalf("cursor agent should get plugin MCP tool name, got %q", ctx)
}
_, ctx = formatFindings("a.py", findings, "", "Claude", "sess-1")
if !strings.Contains(ctx, `ignore-vulnerability --scan-type asca --data '`) {
t.Fatalf("claude agent should get single-quoted suppress command, got %q", ctx)
}
}

func TestAdditionalContext_PinsIgnoredFilePathToWorkDir(t *testing.T) {
findings := []grpcs.ScanDetail{
{FileName: "billing.py", Line: 5, RuleID: 4059},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//go:build !integration

package asca

import "testing"

const wantNormalizedLines = "line1\nline2\nline3"

func TestNormLF_CRLFNormalized(t *testing.T) {
got := normLF("line1\r\nline2\r\nline3")
if got != wantNormalizedLines {
t.Errorf("normLF() = %q, want %q", got, wantNormalizedLines)
}
}

func TestNormLF_BareCRNormalized(t *testing.T) {
got := normLF("line1\rline2\rline3")
if got != wantNormalizedLines {
t.Errorf("normLF() = %q, want %q", got, wantNormalizedLines)
}
}

func TestNormLF_AlreadyLFUnchanged(t *testing.T) {
got := normLF("line1\nline2\nline3")
if got != wantNormalizedLines {
t.Errorf("normLF() = %q, want %q", got, wantNormalizedLines)
}
}
Loading
Loading