From a00cfbab90cbd4e3ed997edfd25a204858eadb3b Mon Sep 17 00:00:00 2001 From: attiasas Date: Mon, 25 Nov 2024 08:16:52 +0200 Subject: [PATCH 01/34] Add JAS violations --- go.mod | 32 +++--- go.sum | 58 ++++------ scanpullrequest/scanpullrequest.go | 32 +++--- scanpullrequest/scanpullrequest_test.go | 4 +- scanrepository/scanrepository.go | 2 +- utils/analytics_test.go | 8 +- utils/comment.go | 2 +- utils/issuescollection.go | 141 ++++++++++++++++++------ utils/scandetails.go | 8 +- utils/utils.go | 12 +- 10 files changed, 186 insertions(+), 113 deletions(-) diff --git a/go.mod b/go.mod index 47d274f2a..ad29e51fe 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/jfrog/frogbot/v2 -go 1.22.7 +go 1.22.9 + +toolchain go1.23.3 require ( github.com/go-git/go-git/v5 v5.12.0 @@ -17,7 +19,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.4 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f gopkg.in/yaml.v3 v3.0.1 ) @@ -26,7 +28,7 @@ require ( github.com/BurntSushi/toml v1.4.0 // indirect github.com/CycloneDX/cyclonedx-go v0.9.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/ProtonMail/go-crypto v1.1.2 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/c-bata/go-prompt v0.2.5 // indirect @@ -102,26 +104,30 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/tools v0.27.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) -// replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security dev +// eranturgeman:jas-violations-support +replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security + +// replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go dev +// attiasas:add_repo_context_scan_graph +replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241124184755-a8b7f3de7938 // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go dev diff --git a/go.sum b/go.sum index 9c139e066..8d0cdaefa 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKc github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.2 h1:A7JbD57ThNqh7XjmHE+PXpQ3Dqt3BrSAC0AL0Go3KS0= +github.com/ProtonMail/go-crypto v1.1.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -22,11 +22,12 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/attiasas/jfrog-client-go v0.0.0-20241124184755-a8b7f3de7938 h1:r1GGpcS6w3Dy1NKZqR5r4qyT9XWa5XgYnd7+LPBv4Ik= +github.com/attiasas/jfrog-client-go v0.0.0-20241124184755-a8b7f3de7938/go.mod h1:1a7bmQHkRmPEza9wva2+WVrYzrGbosrMymq57kyG5gU= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.5 h1:3zg6PecEywxNn0xiqcXHD96fkbxghD+gdB2tbsYfl+Y= github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -38,7 +39,6 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= @@ -133,10 +133,6 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-core/v2 v2.56.7 h1:pB4ronzVk60k/lf9bUL9HxBZ8PbMW6LhbIFld9NXNNc= github.com/jfrog/jfrog-cli-core/v2 v2.56.7/go.mod h1:puLwWcnXYCJqUOvhscXRJiKNzPdj0adP+zadKy6A/gU= -github.com/jfrog/jfrog-cli-security v1.12.5 h1:2JHPyapXuHQw/qEaElGxBUGrJCZlVFLXDdxkqhf10vE= -github.com/jfrog/jfrog-cli-security v1.12.5/go.mod h1:5LBGwth7TXkEH8MO0JJXvpoRktMAV2BK7Q5nQePNrv4= -github.com/jfrog/jfrog-client-go v1.47.6 h1:nEMwJvjsuuY6LpOV3e33P4c4irPHkG8Qxw27bgeCl/Y= -github.com/jfrog/jfrog-client-go v1.47.6/go.mod h1:jCpvS83DZHAin2aSG7VroTsILJsyq7AOcFfx++P241E= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= @@ -297,19 +293,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -320,15 +314,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -338,8 +330,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -364,38 +356,32 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -403,8 +389,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= 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= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 43cdd3c61..58b6b7eba 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -103,7 +103,7 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er } // Output results - shouldSendExposedSecretsEmail := issues.SecretsExists() && repo.SmtpServer != "" + shouldSendExposedSecretsEmail := issues.SecretsIssuesExists() && repo.SmtpServer != "" if shouldSendExposedSecretsEmail { secretsEmailDetails := utils.NewSecretsEmailDetails(client, repo, issues.Secrets) if err = utils.AlertSecretsExposed(secretsEmailDetails); err != nil { @@ -184,7 +184,7 @@ func auditPullRequestInProject(repoConfig *utils.Repository, scanDetails *utils. } // Set JAS output flags - repoConfig.OutputWriter.SetJasOutputFlags(sourceResults.EntitledForJas, len(sourceResults.GetJasScansResults(jasutils.Applicability)) > 0) + repoConfig.OutputWriter.SetJasOutputFlags(sourceResults.EntitledForJas, sourceResults.HasJasScansResults(jasutils.Applicability)) // Get all issues that exist in the source branch if repoConfig.IncludeAllVulnerabilities { @@ -300,11 +300,11 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] return nil, err } return &utils.IssuesCollection{ - Vulnerabilities: append(simpleJsonResults.Vulnerabilities, simpleJsonResults.SecurityViolations...), - Iacs: simpleJsonResults.Iacs, - Secrets: simpleJsonResults.Secrets, - Sast: simpleJsonResults.Sast, - Licenses: simpleJsonResults.LicensesViolations, + Vulnerabilities: append(simpleJsonResults.Vulnerabilities, simpleJsonResults.SecurityViolations...), + Iacs: simpleJsonResults.Iacs, + Secrets: simpleJsonResults.Secrets, + Sast: simpleJsonResults.Sast, + LicensesViolations: simpleJsonResults.LicensesViolations, }, nil } @@ -335,8 +335,12 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe } var newIacs []formats.SourceCodeRow - if len(simpleJsonSource.Iacs) > 0 { - newIacs = createNewSourceCodeRows(simpleJsonTarget.Iacs, simpleJsonSource.Iacs) + if len(simpleJsonSource.IacsVulnerabilities) > 0 { + newIacs = createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities) + } + if len(simpleJsonSource.IacsViolations) > 0 { + newIacs = append(newIacs, createNewSourceCodeRows(simpleJsonTarget.IacsViolations, simpleJsonSource.IacsViolations)...) + } var newSecrets []formats.SourceCodeRow if len(simpleJsonSource.Secrets) > 0 { @@ -348,11 +352,11 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe } return &utils.IssuesCollection{ - Vulnerabilities: newVulnerabilitiesOrViolations, - Iacs: newIacs, - Secrets: newSecrets, - Sast: newSast, - Licenses: newLicenses, + Vulnerabilities: newVulnerabilitiesOrViolations, + Iacs: newIacs, + Secrets: newSecrets, + Sast: newSast, + LicensesViolations: newLicenses, }, nil } diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index e85fa0a12..9f7cee047 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -555,7 +555,7 @@ func TestGetAllIssues(t *testing.T) { }, }, }, - Licenses: []formats.LicenseRow{ + LicensesViolations: []formats.LicenseRow{ { LicenseKey: "Apache-2.0", ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ @@ -576,7 +576,7 @@ func TestGetAllIssues(t *testing.T) { assert.ElementsMatch(t, expectedOutput.Iacs, issuesRows.Iacs) assert.ElementsMatch(t, expectedOutput.Secrets, issuesRows.Secrets) assert.ElementsMatch(t, expectedOutput.Sast, issuesRows.Sast) - assert.ElementsMatch(t, expectedOutput.Licenses, issuesRows.Licenses) + assert.ElementsMatch(t, expectedOutput.LicensesViolations, issuesRows.LicensesViolations) } } diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index ae23bd39c..e0adbd470 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -218,7 +218,7 @@ func (cfp *ScanRepositoryCmd) scan(currentWorkingDir string) (*results.SecurityC return nil, err } log.Info("Xray scan completed") - cfp.OutputWriter.SetJasOutputFlags(auditResults.EntitledForJas, len(auditResults.GetJasScansResults(jasutils.Applicability)) > 0) + cfp.OutputWriter.SetJasOutputFlags(auditResults.EntitledForJas, auditResults.HasJasScansResults(jasutils.Applicability)) cfp.projectTech = auditResults.GetTechnologies(cfp.projectTech...) return auditResults, nil } diff --git a/utils/analytics_test.go b/utils/analytics_test.go index fbe6b6310..593e14a28 100644 --- a/utils/analytics_test.go +++ b/utils/analytics_test.go @@ -18,10 +18,10 @@ func TestCreateAnalyticsGeneralEvent(t *testing.T) { GitProvider: "GitHub", Technologies: nil, BranchName: "main", - LastCommit: "https://api.github.com/repos/my-user/my-project/commits/a23ba44a0d379dida668nmb72003a82e4e11d0ba", - CommitHash: "a23ba44a0d379dida668nmb72003a82e4e11d0ba", - CommitMessage: ".", - CommitAuthor: "User", + LastCommitUrl: "https://api.github.com/repos/my-user/my-project/commits/a23ba44a0d379dida668nmb72003a82e4e11d0ba", + LastCommitHash: "a23ba44a0d379dida668nmb72003a82e4e11d0ba", + LastCommitMessage: ".", + LastCommitAuthor: "User", } serverDetails := &config.ServerDetails{ diff --git a/utils/comment.go b/utils/comment.go index b98705aba..916a4b17c 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -114,7 +114,7 @@ func generatePullRequestSummaryComment(issuesCollection *IssuesCollection, write if vulnerabilitiesContent := outputwriter.VulnerabilitiesContent(issuesCollection.Vulnerabilities, writer); len(vulnerabilitiesContent) > 0 { content = append(content, vulnerabilitiesContent...) } - if licensesContent := outputwriter.LicensesContent(issuesCollection.Licenses, writer); len(licensesContent) > 0 { + if licensesContent := outputwriter.LicensesContent(issuesCollection.LicensesViolations, writer); len(licensesContent) > 0 { content = append(content, licensesContent) } return outputwriter.GetPRSummaryContent(content, true, true, writer) diff --git a/utils/issuescollection.go b/utils/issuescollection.go index 73c338d78..cc857082e 100644 --- a/utils/issuescollection.go +++ b/utils/issuescollection.go @@ -6,71 +6,144 @@ import ( ) type IssuesCollection struct { - Vulnerabilities []formats.VulnerabilityOrViolationRow - Iacs []formats.SourceCodeRow - Secrets []formats.SourceCodeRow - Sast []formats.SourceCodeRow - Licenses []formats.LicenseRow + ScaVulnerabilities []formats.VulnerabilityOrViolationRow + IacVulnerabilities []formats.SourceCodeRow + SecretsVulnerabilities []formats.SourceCodeRow + SastVulnerabilities []formats.SourceCodeRow + + ScaViolations []formats.VulnerabilityOrViolationRow + LicensesViolations []formats.LicenseRow + IacViolations []formats.SourceCodeRow + SecretsViolations []formats.SourceCodeRow + SastViolations []formats.SourceCodeRow } -func (ic *IssuesCollection) VulnerabilitiesExists() bool { - return len(ic.Vulnerabilities) > 0 +// func (ic *IssuesCollection) GetUniqueScaIssues() (unique []formats.VulnerabilityOrViolationRow) { +// parsedIssues := datastructures.MakeSet[string]() +// for _, issue := range ic.ScaViolations { +// if !parsedIssues.Exists(issue.IssueId + "|" + component.Name + "|" + component.Version) { +// unique = append(unique, issue) +// parsedIssues.Add(issue.IssueId) +// } +// } +// for _, issue := range ic.ScaVulnerabilities { +// if !parsedIssues.Exists(issue.IssueId) { +// unique = append(unique, issue) +// parsedIssues.Add(issue.IssueId) +// } +// } +// return +// } + +func (ic *IssuesCollection) ScaIssuesExists() bool { + return len(ic.ScaVulnerabilities) > 0 || len(ic.ScaViolations) > 0 } -func (ic *IssuesCollection) IacExists() bool { - return len(ic.Iacs) > 0 +func (ic *IssuesCollection) IacIssuesExists() bool { + return len(ic.IacVulnerabilities) > 0 || len(ic.IacViolations) > 0 } -func (ic *IssuesCollection) LicensesExists() bool { - return len(ic.Licenses) > 0 +func (ic *IssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { + parsedIssues := datastructures.MakeSet[string]() + for _, issue := range ic.SecretsViolations { + issueId := issue.Location + if !parsedIssues.Exists(issue.SecretsViolations) { + unique = append(unique, issue) + parsedIssues.Add(issue.IssueId) + } + } + for _, issue := range ic.SastVulnerabilities { + if !parsedIssues.Exists(issue.IssueId) { + unique = append(unique, issue) + parsedIssues.Add(issue.IssueId) + } + } + return } -func (ic *IssuesCollection) SecretsExists() bool { - return len(ic.Secrets) > 0 +func (ic *IssuesCollection) SecretsIssuesExists() bool { + return len(ic.SecretsVulnerabilities) > 0 || len(ic.SecretsViolations) > 0 } -func (ic *IssuesCollection) SastExists() bool { - return len(ic.Sast) > 0 +func (ic *IssuesCollection) SastIssuesExists() bool { + return len(ic.SastVulnerabilities) > 0 || len(ic.SastViolations) > 0 } -func (ic *IssuesCollection) IssuesExists() bool { - return ic.VulnerabilitiesExists() || ic.IacExists() || ic.LicensesExists() || ic.SastExists() +func (ic *IssuesCollection) LicensesViolationsExists() bool { + return len(ic.LicensesViolations) > 0 +} + +func (ic *IssuesCollection) PresentableIssuesExists() bool { + return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.LicensesViolationsExists() || ic.SastIssuesExists() } func (ic *IssuesCollection) Append(issues *IssuesCollection) { if issues == nil { return } - if len(issues.Vulnerabilities) > 0 { - ic.Vulnerabilities = append(ic.Vulnerabilities, issues.Vulnerabilities...) + if len(issues.ScaVulnerabilities) > 0 { + ic.ScaVulnerabilities = append(ic.ScaVulnerabilities, issues.ScaVulnerabilities...) + } + if len(issues.ScaViolations) > 0 { + ic.ScaViolations = append(ic.ScaViolations, issues.ScaViolations...) + + } + if len(issues.SecretsVulnerabilities) > 0 { + ic.SecretsVulnerabilities = append(ic.SecretsVulnerabilities, issues.SecretsVulnerabilities...) + } + if len(issues.SecretsViolations) > 0 { + ic.SecretsViolations = append(ic.SecretsViolations, issues.SecretsViolations...) } - if len(issues.Secrets) > 0 { - ic.Secrets = append(ic.Secrets, issues.Secrets...) + if len(issues.SastVulnerabilities) > 0 { + ic.SastVulnerabilities = append(ic.SastVulnerabilities, issues.SastVulnerabilities...) } - if len(issues.Sast) > 0 { - ic.Sast = append(ic.Sast, issues.Sast...) + if len(issues.SastViolations) > 0 { + ic.SastViolations = append(ic.SastViolations, issues.SastViolations...) } - if len(issues.Iacs) > 0 { - ic.Iacs = append(ic.Iacs, issues.Iacs...) + if len(issues.IacVulnerabilities) > 0 { + ic.IacVulnerabilities = append(ic.IacVulnerabilities, issues.IacVulnerabilities...) } - if len(issues.Licenses) > 0 { - ic.Licenses = append(ic.Licenses, issues.Licenses...) + if len(issues.IacViolations) > 0 { + ic.IacViolations = append(ic.IacViolations, issues.IacViolations...) + } + if len(issues.LicensesViolations) > 0 { + ic.LicensesViolations = append(ic.LicensesViolations, issues.LicensesViolations...) } } func (ic *IssuesCollection) CountIssuesCollectionFindings() int { uniqueFindings := datastructures.MakeSet[string]() - var totalFindings int - for _, vulnerability := range ic.Vulnerabilities { + for _, vulnerability := range ic.ScaVulnerabilities { for _, component := range vulnerability.Components { uniqueFindings.Add(vulnerability.IssueId + "|" + component.Name + "|" + component.Version) } } - totalFindings += uniqueFindings.Size() + for _, violations := range ic.ScaViolations { + for _, component := range violations.Components { + uniqueFindings.Add(violations.IssueId + "|" + component.Name + "|" + component.Version) + } + } + for _, vulnerability := range ic.IacVulnerabilities { + uniqueFindings.Add(vulnerability.ToString() + "|" + vulnerability.Finding) + } + for _, violations := range ic.IacViolations { + uniqueFindings.Add(violations.ToString() + "|" + violations.Finding) + } + for _, vulnerability := range ic.SecretsVulnerabilities { + uniqueFindings.Add(vulnerability.ToString() + "|" + vulnerability.Finding) + } + for _, violations := range ic.SecretsViolations { + uniqueFindings.Add(violations.ToString() + "|" + violations.Finding) + } + for _, vulnerability := range ic.SastVulnerabilities { + uniqueFindings.Add(vulnerability.ToString() + "|" + vulnerability.Finding) + } + for _, violations := range ic.SastViolations { + uniqueFindings.Add(violations.ToString() + "|" + violations.Finding) + } - totalFindings += len(ic.Iacs) - totalFindings += len(ic.Sast) - totalFindings += len(ic.Secrets) - return totalFindings + return uniqueFindings.Size() } + + diff --git a/utils/scandetails.go b/utils/scandetails.go index ca7189559..930c733ba 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -233,10 +233,10 @@ func (sc *ScanDetails) createGitInfoContext(scannedBranch, gitProject string, cl GitProvider: sc.GitProvider.String(), GitProject: gitProject, BranchName: scannedBranch, - LastCommit: latestCommit.Url, - CommitHash: latestCommit.Hash, - CommitMessage: latestCommit.Message, - CommitAuthor: latestCommit.AuthorName, + LastCommitUrl: latestCommit.Url, + LastCommitHash: latestCommit.Hash, + LastCommitMessage: latestCommit.Message, + LastCommitAuthor: latestCommit.AuthorName, } return } diff --git a/utils/utils.go b/utils/utils.go index 6af390241..13a21eaa4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -337,10 +337,14 @@ func GetVulnerabiltiesUniqueID(vulnerability formats.VulnerabilityOrViolationRow } func ConvertSarifPathsToRelative(issues *IssuesCollection, workingDirs ...string) { - convertSarifPathsInCveApplicability(issues.Vulnerabilities, workingDirs...) - convertSarifPathsInIacs(issues.Iacs, workingDirs...) - convertSarifPathsInSecrets(issues.Secrets, workingDirs...) - convertSarifPathsInSast(issues.Sast, workingDirs...) + convertSarifPathsInCveApplicability(issues.ScaVulnerabilities, workingDirs...) + convertSarifPathsInIacs(issues.IacsVulnerabilities, workingDirs...) + convertSarifPathsInSecrets(issues.SecretsVulnerabilities, workingDirs...) + convertSarifPathsInSast(issues.SastVulnerabilities, workingDirs...) + convertSarifPathsInCveApplicability(issues.ScaViolations, workingDirs...) + convertSarifPathsInIacs(issues.IacsViolations, workingDirs...) + convertSarifPathsInSecrets(issues.SecretsViolations, workingDirs...) + convertSarifPathsInSast(issues.SastViolations, workingDirs...) } func convertSarifPathsInCveApplicability(vulnerabilities []formats.VulnerabilityOrViolationRow, workingDirs ...string) { From 5ae602b8771cc0d737aca340d2888344f56ca59d Mon Sep 17 00:00:00 2001 From: attiasas Date: Mon, 25 Nov 2024 11:09:52 +0200 Subject: [PATCH 02/34] continue --- scanpullrequest/scanpullrequest.go | 19 +++++++++++++------ utils/issuescollection.go | 21 ++++++++++++--------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 58b6b7eba..cd53da0fa 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -105,7 +105,7 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er // Output results shouldSendExposedSecretsEmail := issues.SecretsIssuesExists() && repo.SmtpServer != "" if shouldSendExposedSecretsEmail { - secretsEmailDetails := utils.NewSecretsEmailDetails(client, repo, issues.Secrets) + secretsEmailDetails := utils.NewSecretsEmailDetails(client, repo, issues.GetUniqueSecretsIssues()) if err = utils.AlertSecretsExposed(secretsEmailDetails); err != nil { return } @@ -126,7 +126,7 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er func toFailTaskStatus(repo *utils.Repository, issues *utils.IssuesCollection) bool { failFlagSet := repo.FailOnSecurityIssues != nil && *repo.FailOnSecurityIssues - return failFlagSet && issues.IssuesExists() + return failFlagSet && issues.PresentableIssuesExists() } // Downloads Pull Requests branches code and audits them @@ -300,11 +300,18 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] return nil, err } return &utils.IssuesCollection{ - Vulnerabilities: append(simpleJsonResults.Vulnerabilities, simpleJsonResults.SecurityViolations...), - Iacs: simpleJsonResults.Iacs, - Secrets: simpleJsonResults.Secrets, - Sast: simpleJsonResults.Sast, + ScaVulnerabilities: simpleJsonResults.Vulnerabilities, + ScaViolations: simpleJsonResults.SecurityViolations, LicensesViolations: simpleJsonResults.LicensesViolations, + + IacVulnerabilities: simpleJsonResults.IacsVulnerabilities, + IacViolations: simpleJsonResults.IacsViolations, + + SecretsVulnerabilities: simpleJsonResults.SecretsVulnerabilities, + SecretsViolations: simpleJsonResults.SecretsViolations, + + SastVulnerabilities: simpleJsonResults.SastVulnerabilities, + SastViolations: simpleJsonResults.SastViolations, }, nil } diff --git a/utils/issuescollection.go b/utils/issuescollection.go index cc857082e..dfc4e8b18 100644 --- a/utils/issuescollection.go +++ b/utils/issuescollection.go @@ -45,18 +45,21 @@ func (ic *IssuesCollection) IacIssuesExists() bool { func (ic *IssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { parsedIssues := datastructures.MakeSet[string]() - for _, issue := range ic.SecretsViolations { - issueId := issue.Location - if !parsedIssues.Exists(issue.SecretsViolations) { - unique = append(unique, issue) - parsedIssues.Add(issue.IssueId) + for _, violation := range ic.SecretsViolations { + issueId := violation.ToString() + "|" + violation.Finding + if parsedIssues.Exists(issueId) { + continue } + parsedIssues.Add(issueId) + unique = append(unique, violation) } - for _, issue := range ic.SastVulnerabilities { - if !parsedIssues.Exists(issue.IssueId) { - unique = append(unique, issue) - parsedIssues.Add(issue.IssueId) + for _, vulnerability := range ic.SecretsVulnerabilities { + issueId := vulnerability.ToString() + "|" + vulnerability.Finding + if parsedIssues.Exists(issueId) { + continue } + parsedIssues.Add(issueId) + unique = append(unique, vulnerability) } return } From 349feeba0479a84755c148f67c681a3161839ae4 Mon Sep 17 00:00:00 2001 From: attiasas Date: Mon, 25 Nov 2024 14:09:26 +0200 Subject: [PATCH 03/34] continue --- scanpullrequest/scanpullrequest.go | 45 ++++++------------------------ utils/comment.go | 4 +-- utils/utils.go | 4 +-- 3 files changed, 13 insertions(+), 40 deletions(-) diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index cd53da0fa..3ed989df9 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -327,43 +327,16 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe if err != nil { return nil, err } - - var newVulnerabilitiesOrViolations []formats.VulnerabilityOrViolationRow - if len(simpleJsonSource.Vulnerabilities) > 0 || len(simpleJsonSource.SecurityViolations) > 0 { - newVulnerabilitiesOrViolations = append( - getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities), - getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.SecurityViolations, simpleJsonSource.SecurityViolations)..., - ) - } - - var newLicenses []formats.LicenseRow - if len(simpleJsonSource.LicensesViolations) > 0 { - newLicenses = getUniqueLicenseRows(simpleJsonTarget.LicensesViolations, simpleJsonSource.LicensesViolations) - } - - var newIacs []formats.SourceCodeRow - if len(simpleJsonSource.IacsVulnerabilities) > 0 { - newIacs = createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities) - } - if len(simpleJsonSource.IacsViolations) > 0 { - newIacs = append(newIacs, createNewSourceCodeRows(simpleJsonTarget.IacsViolations, simpleJsonSource.IacsViolations)...) - - } - var newSecrets []formats.SourceCodeRow - if len(simpleJsonSource.Secrets) > 0 { - newSecrets = createNewSourceCodeRows(simpleJsonTarget.Secrets, simpleJsonSource.Secrets) - } - var newSast []formats.SourceCodeRow - if len(simpleJsonSource.Sast) > 0 { - newSast = createNewSourceCodeRows(simpleJsonTarget.Sast, simpleJsonSource.Sast) - } - return &utils.IssuesCollection{ - Vulnerabilities: newVulnerabilitiesOrViolations, - Iacs: newIacs, - Secrets: newSecrets, - Sast: newSast, - LicensesViolations: newLicenses, + ScaVulnerabilities: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities), + ScaViolations: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.SecurityViolations, simpleJsonSource.SecurityViolations), + IacVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities), + IacViolations: createNewSourceCodeRows(simpleJsonTarget.IacsViolations, simpleJsonSource.IacsViolations), + SecretsVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.SecretsVulnerabilities, simpleJsonSource.SecretsVulnerabilities), + SecretsViolations: createNewSourceCodeRows(simpleJsonTarget.SecretsViolations, simpleJsonSource.SecretsViolations), + SastVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.SastVulnerabilities, simpleJsonSource.SastVulnerabilities), + SastViolations: createNewSourceCodeRows(simpleJsonTarget.SastViolations, simpleJsonSource.SastViolations), + LicensesViolations: getUniqueLicenseRows(simpleJsonTarget.LicensesViolations, simpleJsonSource.LicensesViolations), }, nil } diff --git a/utils/comment.go b/utils/comment.go index 916a4b17c..b90237b7e 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -106,12 +106,12 @@ func GenerateFixPullRequestDetails(vulnerabilities []formats.VulnerabilityOrViol } func generatePullRequestSummaryComment(issuesCollection *IssuesCollection, writer outputwriter.OutputWriter) []string { - if !issuesCollection.IssuesExists() { + if !issuesCollection.PresentableIssuesExists() { return outputwriter.GetPRSummaryContent([]string{}, false, true, writer) } content := []string{} - if vulnerabilitiesContent := outputwriter.VulnerabilitiesContent(issuesCollection.Vulnerabilities, writer); len(vulnerabilitiesContent) > 0 { + if vulnerabilitiesContent := outputwriter.VulnerabilitiesContent(append(issuesCollection.ScaVulnerabilities, issuesCollection.ScaViolations...), writer); len(vulnerabilitiesContent) > 0 { content = append(content, vulnerabilitiesContent...) } if licensesContent := outputwriter.LicensesContent(issuesCollection.LicensesViolations, writer); len(licensesContent) > 0 { diff --git a/utils/utils.go b/utils/utils.go index 13a21eaa4..de88591e7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -338,11 +338,11 @@ func GetVulnerabiltiesUniqueID(vulnerability formats.VulnerabilityOrViolationRow func ConvertSarifPathsToRelative(issues *IssuesCollection, workingDirs ...string) { convertSarifPathsInCveApplicability(issues.ScaVulnerabilities, workingDirs...) - convertSarifPathsInIacs(issues.IacsVulnerabilities, workingDirs...) + convertSarifPathsInIacs(issues.IacVulnerabilities, workingDirs...) convertSarifPathsInSecrets(issues.SecretsVulnerabilities, workingDirs...) convertSarifPathsInSast(issues.SastVulnerabilities, workingDirs...) convertSarifPathsInCveApplicability(issues.ScaViolations, workingDirs...) - convertSarifPathsInIacs(issues.IacsViolations, workingDirs...) + convertSarifPathsInIacs(issues.IacViolations, workingDirs...) convertSarifPathsInSecrets(issues.SecretsViolations, workingDirs...) convertSarifPathsInSast(issues.SastViolations, workingDirs...) } From c6493c36c80f4b496559cd5348b3bd8d9dd6d3e9 Mon Sep 17 00:00:00 2001 From: attiasas Date: Mon, 25 Nov 2024 17:14:04 +0200 Subject: [PATCH 04/34] done breaking changes --- go.mod | 7 +- go.sum | 16 ++--- scanpullrequest/scanpullrequest.go | 24 +++---- scanpullrequest/scanpullrequest_test.go | 86 +++++++++++----------- utils/analytics_test.go | 20 +++--- utils/comment.go | 8 +-- utils/comment_test.go | 10 +-- utils/issuescollection.go | 96 +++++++++---------------- utils/issuescollection_test.go | 8 +-- utils/scandetails.go | 18 ++--- 10 files changed, 129 insertions(+), 164 deletions(-) diff --git a/go.mod b/go.mod index 5efe7ffbd..4c69b1c3c 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,6 @@ module github.com/jfrog/frogbot/v2 go 1.22.9 -toolchain go1.23.3 -go 1.22.9 - toolchain go1.23.3 require ( @@ -126,7 +123,7 @@ require ( ) // eranturgeman:jas-violations-support -replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241125121334-069dbae0f2e9 // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc @@ -135,6 +132,6 @@ replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev // attiasas:add_repo_context_scan_graph -replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241124184755-a8b7f3de7938 +replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241125121151-c243bb1fe709 // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go dev diff --git a/go.sum b/go.sum index 1b35e1578..ba809620a 100644 --- a/go.sum +++ b/go.sum @@ -22,13 +22,10 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -<<<<<<< HEAD -github.com/attiasas/jfrog-client-go v0.0.0-20241124184755-a8b7f3de7938 h1:r1GGpcS6w3Dy1NKZqR5r4qyT9XWa5XgYnd7+LPBv4Ik= -github.com/attiasas/jfrog-client-go v0.0.0-20241124184755-a8b7f3de7938/go.mod h1:1a7bmQHkRmPEza9wva2+WVrYzrGbosrMymq57kyG5gU= -======= +github.com/attiasas/jfrog-client-go v0.0.0-20241125121151-c243bb1fe709 h1:kgtTR4QpGceQWIvAkWESToWV7xW4RXLbBUTcuPZwB+4= +github.com/attiasas/jfrog-client-go v0.0.0-20241125121151-c243bb1fe709/go.mod h1:1a7bmQHkRmPEza9wva2+WVrYzrGbosrMymq57kyG5gU= github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= ->>>>>>> upstream/dev github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -62,6 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241125121334-069dbae0f2e9 h1:8Efm+GIQvJI9l4Kqq9WQrHZ+/fu7nfR3SwS6P2gYBgU= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241125121334-069dbae0f2e9/go.mod h1:MNeAR2B+YdXgcuGMcTTGJMpUWkVV+meVDvd8Pmh20TU= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= @@ -138,13 +137,6 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-core/v2 v2.56.7 h1:pB4ronzVk60k/lf9bUL9HxBZ8PbMW6LhbIFld9NXNNc= github.com/jfrog/jfrog-cli-core/v2 v2.56.7/go.mod h1:puLwWcnXYCJqUOvhscXRJiKNzPdj0adP+zadKy6A/gU= -<<<<<<< HEAD -======= -github.com/jfrog/jfrog-cli-security v1.13.2-0.20241125090915-8dbf035c0394 h1:ws3gGhXezgv4/zhNt9zSOaKgpZel8qR1T9y8m0ZXIsE= -github.com/jfrog/jfrog-cli-security v1.13.2-0.20241125090915-8dbf035c0394/go.mod h1:dfwS1m/MCz0dHQKmLQhSK1ZcPhuQE0gKAOPug3jLV3Q= -github.com/jfrog/jfrog-client-go v1.28.1-0.20241124172451-50bd3e54f1e0 h1:YROG+bJY4QJEz9KdKUbBlbOHXY1vnDhhi0/cXrEgu9E= -github.com/jfrog/jfrog-client-go v1.28.1-0.20241124172451-50bd3e54f1e0/go.mod h1:1a7bmQHkRmPEza9wva2+WVrYzrGbosrMymq57kyG5gU= ->>>>>>> upstream/dev github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 72285d4f0..3e57c38b8 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -304,17 +304,17 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] } return &utils.IssuesCollection{ ScaVulnerabilities: simpleJsonResults.Vulnerabilities, - ScaViolations: simpleJsonResults.SecurityViolations, + ScaViolations: simpleJsonResults.SecurityViolations, LicensesViolations: simpleJsonResults.LicensesViolations, IacVulnerabilities: simpleJsonResults.IacsVulnerabilities, IacViolations: simpleJsonResults.IacsViolations, SecretsVulnerabilities: simpleJsonResults.SecretsVulnerabilities, - SecretsViolations: simpleJsonResults.SecretsViolations, + SecretsViolations: simpleJsonResults.SecretsViolations, SastVulnerabilities: simpleJsonResults.SastVulnerabilities, - SastViolations: simpleJsonResults.SastViolations, + SastViolations: simpleJsonResults.SastViolations, }, nil } @@ -331,15 +331,15 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe return nil, err } return &utils.IssuesCollection{ - ScaVulnerabilities: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities), - ScaViolations: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.SecurityViolations, simpleJsonSource.SecurityViolations), - IacVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities), - IacViolations: createNewSourceCodeRows(simpleJsonTarget.IacsViolations, simpleJsonSource.IacsViolations), - SecretsVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.SecretsVulnerabilities, simpleJsonSource.SecretsVulnerabilities), - SecretsViolations: createNewSourceCodeRows(simpleJsonTarget.SecretsViolations, simpleJsonSource.SecretsViolations), - SastVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.SastVulnerabilities, simpleJsonSource.SastVulnerabilities), - SastViolations: createNewSourceCodeRows(simpleJsonTarget.SastViolations, simpleJsonSource.SastViolations), - LicensesViolations: getUniqueLicenseRows(simpleJsonTarget.LicensesViolations, simpleJsonSource.LicensesViolations), + ScaVulnerabilities: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities), + ScaViolations: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.SecurityViolations, simpleJsonSource.SecurityViolations), + IacVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities), + IacViolations: createNewSourceCodeRows(simpleJsonTarget.IacsViolations, simpleJsonSource.IacsViolations), + SecretsVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.SecretsVulnerabilities, simpleJsonSource.SecretsVulnerabilities), + SecretsViolations: createNewSourceCodeRows(simpleJsonTarget.SecretsViolations, simpleJsonSource.SecretsViolations), + SastVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.SastVulnerabilities, simpleJsonSource.SastVulnerabilities), + SastViolations: createNewSourceCodeRows(simpleJsonTarget.SastViolations, simpleJsonSource.SastViolations), + LicensesViolations: getUniqueLicenseRows(simpleJsonTarget.LicensesViolations, simpleJsonSource.LicensesViolations), }, nil } diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index b9358fb71..2868fcd74 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -62,7 +62,7 @@ func scaToDummySimpleJsonResults(response services.ScanResponse, applicable bool convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true, AllowedLicenses: allowedLicenses}) jasResults := &results.JasScansResults{} if applicable { - jasResults.ApplicabilityScanResults = append(jasResults.ApplicabilityScanResults, sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2023-4321", ""))) + jasResults.JasVulnerabilities.ApplicabilityScanResults = append(jasResults.JasVulnerabilities.ApplicabilityScanResults, sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2023-4321", ""))) } cmdResults := &results.SecurityCommandResults{EntitledForJas: applicable, Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "dummy"}, @@ -81,14 +81,14 @@ func createJasDiff(t *testing.T, scanType jasutils.JasScanType, source, target [ var targetJasResults, sourceJasResults []formats.SourceCodeRow switch scanType { case jasutils.Sast: - targetJasResults = targetResults.Sast - sourceJasResults = sourceResults.Sast + targetJasResults = targetResults.SastVulnerabilities + sourceJasResults = sourceResults.SastVulnerabilities case jasutils.Secrets: - targetJasResults = targetResults.Secrets - sourceJasResults = sourceResults.Secrets + targetJasResults = targetResults.SecretsVulnerabilities + sourceJasResults = sourceResults.SecretsVulnerabilities case jasutils.IaC: - targetJasResults = targetResults.Iacs - sourceJasResults = sourceResults.Iacs + targetJasResults = targetResults.IacsVulnerabilities + sourceJasResults = sourceResults.IacsVulnerabilities } return createNewSourceCodeRows(targetJasResults, sourceJasResults) @@ -97,14 +97,14 @@ func createJasDiff(t *testing.T, scanType jasutils.JasScanType, source, target [ func jasToDummySimpleJsonResults(scanType jasutils.JasScanType, jasScanResults []*sarif.Result) (formats.SimpleJsonResults, error) { convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true}) - jasResults := &results.JasScansResults{} + jasResults := &results.JasScansResults{JasVulnerabilities: &results.JasScanResults{}} switch scanType { case jasutils.Sast: - jasResults.SastScanResults = append(jasResults.SastScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) + jasResults.JasVulnerabilities.SastScanResults = append(jasResults.JasVulnerabilities.SastScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) case jasutils.Secrets: - jasResults.SecretsScanResults = append(jasResults.SecretsScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) + jasResults.JasVulnerabilities.SecretsScanResults = append(jasResults.JasVulnerabilities.SecretsScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) case jasutils.IaC: - jasResults.IacScanResults = append(jasResults.IacScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) + jasResults.JasVulnerabilities.IacScanResults = append(jasResults.JasVulnerabilities.IacScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) } cmdResults := &results.SecurityCommandResults{EntitledForJas: true, Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "dummy"}, @@ -455,37 +455,39 @@ func TestGetAllIssues(t *testing.T) { }}, }, JasResults: &results.JasScansResults{ - ApplicabilityScanResults: []*sarif.Run{ - sarifutils.CreateRunWithDummyResults( - sarifutils.CreateDummyPassingResult("applic_CVE-2023-3122"), - sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2022-2122", ""), - ), - }, - IacScanResults: []*sarif.Run{ - sarifutils.CreateRunWithDummyResults( - sarifutils.CreateResultWithLocations("Missing auto upgrade was detected", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), - sarifutils.CreateLocation("file1", 1, 10, 2, 11, "aws-violation"), + JasVulnerabilities: &results.JasScanResults{ + ApplicabilityScanResults: []*sarif.Run{ + sarifutils.CreateRunWithDummyResults( + sarifutils.CreateDummyPassingResult("applic_CVE-2023-3122"), + sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2022-2122", ""), ), - ), - }, - SecretsScanResults: []*sarif.Run{ - sarifutils.CreateRunWithDummyResults( - sarifutils.CreateResultWithLocations("Secret", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), - sarifutils.CreateLocation("index.js", 5, 6, 7, 8, "access token exposed"), + }, + IacScanResults: []*sarif.Run{ + sarifutils.CreateRunWithDummyResults( + sarifutils.CreateResultWithLocations("Missing auto upgrade was detected", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), + sarifutils.CreateLocation("file1", 1, 10, 2, 11, "aws-violation"), + ), ), - ), - }, - SastScanResults: []*sarif.Run{ - sarifutils.CreateRunWithDummyResults( - sarifutils.CreateResultWithLocations("XSS Vulnerability", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), - sarifutils.CreateLocation("file1", 1, 10, 2, 11, "snippet"), + }, + SecretsScanResults: []*sarif.Run{ + sarifutils.CreateRunWithDummyResults( + sarifutils.CreateResultWithLocations("Secret", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), + sarifutils.CreateLocation("index.js", 5, 6, 7, 8, "access token exposed"), + ), ), - ), + }, + SastScanResults: []*sarif.Run{ + sarifutils.CreateRunWithDummyResults( + sarifutils.CreateResultWithLocations("XSS Vulnerability", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), + sarifutils.CreateLocation("file1", 1, 10, 2, 11, "snippet"), + ), + ), + }, }, }, }}} expectedOutput := &utils.IssuesCollection{ - Vulnerabilities: []formats.VulnerabilityOrViolationRow{ + ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { Applicable: "Applicable", FixedVersions: []string{"1.2.3"}, @@ -505,7 +507,7 @@ func TestGetAllIssues(t *testing.T) { Cves: []formats.CveRow{{Id: "CVE-2023-3122", Applicability: &formats.Applicability{Status: "Not Applicable"}}}, }, }, - Iacs: []formats.SourceCodeRow{ + IacVulnerabilities: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{ Severity: "High", @@ -522,7 +524,7 @@ func TestGetAllIssues(t *testing.T) { }, }, }, - Secrets: []formats.SourceCodeRow{ + SecretsVulnerabilities: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{ Severity: "High", @@ -539,7 +541,7 @@ func TestGetAllIssues(t *testing.T) { }, }, }, - Sast: []formats.SourceCodeRow{ + SastVulnerabilities: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{ Severity: "High", @@ -573,10 +575,10 @@ func TestGetAllIssues(t *testing.T) { issuesRows, err := getAllIssues(auditResults, allowedLicenses, false) if assert.NoError(t, err) { - assert.ElementsMatch(t, expectedOutput.Vulnerabilities, issuesRows.Vulnerabilities) - assert.ElementsMatch(t, expectedOutput.Iacs, issuesRows.Iacs) - assert.ElementsMatch(t, expectedOutput.Secrets, issuesRows.Secrets) - assert.ElementsMatch(t, expectedOutput.Sast, issuesRows.Sast) + assert.ElementsMatch(t, expectedOutput.ScaVulnerabilities, issuesRows.ScaVulnerabilities) + assert.ElementsMatch(t, expectedOutput.IacVulnerabilities, issuesRows.IacVulnerabilities) + assert.ElementsMatch(t, expectedOutput.SecretsVulnerabilities, issuesRows.SecretsVulnerabilities) + assert.ElementsMatch(t, expectedOutput.SastVulnerabilities, issuesRows.SastVulnerabilities) assert.ElementsMatch(t, expectedOutput.LicensesViolations, issuesRows.LicensesViolations) } } diff --git a/utils/analytics_test.go b/utils/analytics_test.go index 7d9ac97f1..082834a4c 100644 --- a/utils/analytics_test.go +++ b/utils/analytics_test.go @@ -11,16 +11,16 @@ import ( func TestCreateAnalyticsGeneralEvent(t *testing.T) { gitInfoContext := &services.XscGitInfoContext{ - GitRepoUrl: "http://localhost:8080/my-user/my-project.git", - GitRepoName: "my-project", - GitProject: "my-user", - GitProvider: "GitHub", - Technologies: nil, - BranchName: "main", - LastCommitUrl: "https://api.github.com/repos/my-user/my-project/commits/a23ba44a0d379dida668nmb72003a82e4e11d0ba", - LastCommitHash: "a23ba44a0d379dida668nmb72003a82e4e11d0ba", - LastCommitMessage: ".", - LastCommitAuthor: "User", + GitRepoHttpsCloneUrl: "http://localhost:8080/my-user/my-project.git", + GitRepoName: "my-project", + GitProject: "my-user", + GitProvider: "GitHub", + Technologies: nil, + BranchName: "main", + LastCommitUrl: "https://api.github.com/repos/my-user/my-project/commits/a23ba44a0d379dida668nmb72003a82e4e11d0ba", + LastCommitHash: "a23ba44a0d379dida668nmb72003a82e4e11d0ba", + LastCommitMessage: ".", + LastCommitAuthor: "User", } serverDetails := &config.ServerDetails{ diff --git a/utils/comment.go b/utils/comment.go index b90237b7e..f43c9ca44 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -111,7 +111,7 @@ func generatePullRequestSummaryComment(issuesCollection *IssuesCollection, write } content := []string{} - if vulnerabilitiesContent := outputwriter.VulnerabilitiesContent(append(issuesCollection.ScaVulnerabilities, issuesCollection.ScaViolations...), writer); len(vulnerabilitiesContent) > 0 { + if vulnerabilitiesContent := outputwriter.VulnerabilitiesContent(issuesCollection.GetScaIssues(), writer); len(vulnerabilitiesContent) > 0 { content = append(content, vulnerabilitiesContent...) } if licensesContent := outputwriter.LicensesContent(issuesCollection.LicensesViolations, writer); len(licensesContent) > 0 { @@ -186,7 +186,7 @@ func getFrogbotComments(writer outputwriter.OutputWriter, existingComments []vcs func getNewReviewComments(repo *Repository, issues *IssuesCollection) (commentsToAdd []ReviewComment) { writer := repo.OutputWriter - for _, vulnerability := range issues.Vulnerabilities { + for _, vulnerability := range issues.GetScaIssues() { for _, cve := range vulnerability.Cves { if cve.Applicability != nil { for _, evidence := range cve.Applicability.Evidence { @@ -195,10 +195,10 @@ func getNewReviewComments(repo *Repository, issues *IssuesCollection) (commentsT } } } - for _, iac := range issues.Iacs { + for _, iac := range issues.GetUniqueIacIssues() { commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, iac.Location, generateSourceCodeReviewContent(IacComment, iac, writer))) } - for _, sast := range issues.Sast { + for _, sast := range issues.GetUniqueSastIssues() { commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeReviewContent(SastComment, sast, writer))) } return diff --git a/utils/comment_test.go b/utils/comment_test.go index 8168b072d..ac5efab6f 100644 --- a/utils/comment_test.go +++ b/utils/comment_test.go @@ -59,7 +59,7 @@ func TestGetNewReviewComments(t *testing.T) { { name: "No issues for review comments", issues: &IssuesCollection{ - Vulnerabilities: []formats.VulnerabilityOrViolationRow{ + ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { Summary: "summary-2", Applicable: "Applicable", @@ -72,7 +72,7 @@ func TestGetNewReviewComments(t *testing.T) { Technology: techutils.Npm, }, }, - Secrets: []formats.SourceCodeRow{ + SecretsVulnerabilities: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{ Severity: "High", @@ -95,7 +95,7 @@ func TestGetNewReviewComments(t *testing.T) { { name: "With issues for review comments", issues: &IssuesCollection{ - Vulnerabilities: []formats.VulnerabilityOrViolationRow{ + ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { Summary: "summary-2", Applicable: "Applicable", @@ -108,7 +108,7 @@ func TestGetNewReviewComments(t *testing.T) { Technology: techutils.Npm, }, }, - Iacs: []formats.SourceCodeRow{ + IacVulnerabilities: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{ Severity: "High", @@ -125,7 +125,7 @@ func TestGetNewReviewComments(t *testing.T) { }, }, }, - Sast: []formats.SourceCodeRow{ + SastVulnerabilities: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{ Severity: "High", diff --git a/utils/issuescollection.go b/utils/issuescollection.go index dfc4e8b18..10487835b 100644 --- a/utils/issuescollection.go +++ b/utils/issuescollection.go @@ -7,45 +7,48 @@ import ( type IssuesCollection struct { ScaVulnerabilities []formats.VulnerabilityOrViolationRow - IacVulnerabilities []formats.SourceCodeRow + IacVulnerabilities []formats.SourceCodeRow SecretsVulnerabilities []formats.SourceCodeRow SastVulnerabilities []formats.SourceCodeRow - ScaViolations []formats.VulnerabilityOrViolationRow - LicensesViolations []formats.LicenseRow - IacViolations []formats.SourceCodeRow - SecretsViolations []formats.SourceCodeRow - SastViolations []formats.SourceCodeRow + ScaViolations []formats.VulnerabilityOrViolationRow + LicensesViolations []formats.LicenseRow + IacViolations []formats.SourceCodeRow + SecretsViolations []formats.SourceCodeRow + SastViolations []formats.SourceCodeRow } -// func (ic *IssuesCollection) GetUniqueScaIssues() (unique []formats.VulnerabilityOrViolationRow) { -// parsedIssues := datastructures.MakeSet[string]() -// for _, issue := range ic.ScaViolations { -// if !parsedIssues.Exists(issue.IssueId + "|" + component.Name + "|" + component.Version) { -// unique = append(unique, issue) -// parsedIssues.Add(issue.IssueId) -// } -// } -// for _, issue := range ic.ScaVulnerabilities { -// if !parsedIssues.Exists(issue.IssueId) { -// unique = append(unique, issue) -// parsedIssues.Add(issue.IssueId) -// } -// } -// return -// } +func (ic *IssuesCollection) GetScaIssues() (unique []formats.VulnerabilityOrViolationRow) { + return append(ic.ScaVulnerabilities, ic.ScaViolations...) +} func (ic *IssuesCollection) ScaIssuesExists() bool { return len(ic.ScaVulnerabilities) > 0 || len(ic.ScaViolations) > 0 } +func (ic *IssuesCollection) GetUniqueIacIssues() (unique []formats.SourceCodeRow) { + return getUniqueJasIssues(ic.IacVulnerabilities, ic.IacViolations) +} + func (ic *IssuesCollection) IacIssuesExists() bool { return len(ic.IacVulnerabilities) > 0 || len(ic.IacViolations) > 0 } func (ic *IssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { + return getUniqueJasIssues(ic.SecretsVulnerabilities, ic.SecretsViolations) +} + +func (ic *IssuesCollection) SecretsIssuesExists() bool { + return len(ic.SecretsVulnerabilities) > 0 || len(ic.SecretsViolations) > 0 +} + +func (ic *IssuesCollection) GetUniqueSastIssues() (unique []formats.SourceCodeRow) { + return getUniqueJasIssues(ic.SastVulnerabilities, ic.SastViolations) +} + +func getUniqueJasIssues(vulnerabilities, violations []formats.SourceCodeRow) (unique []formats.SourceCodeRow) { parsedIssues := datastructures.MakeSet[string]() - for _, violation := range ic.SecretsViolations { + for _, violation := range violations { issueId := violation.ToString() + "|" + violation.Finding if parsedIssues.Exists(issueId) { continue @@ -53,7 +56,7 @@ func (ic *IssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCod parsedIssues.Add(issueId) unique = append(unique, violation) } - for _, vulnerability := range ic.SecretsVulnerabilities { + for _, vulnerability := range vulnerabilities { issueId := vulnerability.ToString() + "|" + vulnerability.Finding if parsedIssues.Exists(issueId) { continue @@ -64,10 +67,6 @@ func (ic *IssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCod return } -func (ic *IssuesCollection) SecretsIssuesExists() bool { - return len(ic.SecretsVulnerabilities) > 0 || len(ic.SecretsViolations) > 0 -} - func (ic *IssuesCollection) SastIssuesExists() bool { return len(ic.SastVulnerabilities) > 0 || len(ic.SastViolations) > 0 } @@ -89,7 +88,7 @@ func (ic *IssuesCollection) Append(issues *IssuesCollection) { } if len(issues.ScaViolations) > 0 { ic.ScaViolations = append(ic.ScaViolations, issues.ScaViolations...) - + } if len(issues.SecretsVulnerabilities) > 0 { ic.SecretsVulnerabilities = append(ic.SecretsVulnerabilities, issues.SecretsVulnerabilities...) @@ -115,38 +114,13 @@ func (ic *IssuesCollection) Append(issues *IssuesCollection) { } func (ic *IssuesCollection) CountIssuesCollectionFindings() int { - uniqueFindings := datastructures.MakeSet[string]() + count := 0 - for _, vulnerability := range ic.ScaVulnerabilities { - for _, component := range vulnerability.Components { - uniqueFindings.Add(vulnerability.IssueId + "|" + component.Name + "|" + component.Version) - } - } - for _, violations := range ic.ScaViolations { - for _, component := range violations.Components { - uniqueFindings.Add(violations.IssueId + "|" + component.Name + "|" + component.Version) - } - } - for _, vulnerability := range ic.IacVulnerabilities { - uniqueFindings.Add(vulnerability.ToString() + "|" + vulnerability.Finding) - } - for _, violations := range ic.IacViolations { - uniqueFindings.Add(violations.ToString() + "|" + violations.Finding) - } - for _, vulnerability := range ic.SecretsVulnerabilities { - uniqueFindings.Add(vulnerability.ToString() + "|" + vulnerability.Finding) - } - for _, violations := range ic.SecretsViolations { - uniqueFindings.Add(violations.ToString() + "|" + violations.Finding) - } - for _, vulnerability := range ic.SastVulnerabilities { - uniqueFindings.Add(vulnerability.ToString() + "|" + vulnerability.Finding) - } - for _, violations := range ic.SastViolations { - uniqueFindings.Add(violations.ToString() + "|" + violations.Finding) - } + count += len(ic.GetScaIssues()) + count += len(ic.GetUniqueIacIssues()) + count += len(ic.GetUniqueSecretsIssues()) + count += len(ic.GetUniqueSastIssues()) + count += len(ic.LicensesViolations) - return uniqueFindings.Size() + return count } - - diff --git a/utils/issuescollection_test.go b/utils/issuescollection_test.go index d6911f2fb..2c4cf14bf 100644 --- a/utils/issuescollection_test.go +++ b/utils/issuescollection_test.go @@ -9,7 +9,7 @@ import ( func TestCountIssuesCollectionFindings(t *testing.T) { issuesCollection := IssuesCollection{ - Vulnerabilities: []formats.VulnerabilityOrViolationRow{ + ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ Components: []formats.ComponentRow{ @@ -42,17 +42,17 @@ func TestCountIssuesCollectionFindings(t *testing.T) { }, }, - Iacs: []formats.SourceCodeRow{ + IacVulnerabilities: []formats.SourceCodeRow{ { ScannerDescription: "Iac issue", }, }, - Secrets: []formats.SourceCodeRow{ + SecretsVulnerabilities: []formats.SourceCodeRow{ { ScannerDescription: "Secret issue", }, }, - Sast: []formats.SourceCodeRow{ + SastVulnerabilities: []formats.SourceCodeRow{ { ScannerDescription: "Sast issue1", }, diff --git a/utils/scandetails.go b/utils/scandetails.go index fb6667d79..0af8e564a 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -230,15 +230,15 @@ func (sc *ScanDetails) createGitInfoContext(scannedBranch, gitProject string, cl } gitInfo = &services.XscGitInfoContext{ // Use Clone URLs as Repo Url, on browsers it will redirect to repository URLS. - GitRepoUrl: sc.Git.RepositoryCloneUrl, - GitRepoName: sc.RepoName, - GitProvider: sc.GitProvider.String(), - GitProject: gitProject, - BranchName: scannedBranch, - LastCommitUrl: latestCommit.Url, - LastCommitHash: latestCommit.Hash, - LastCommitMessage: latestCommit.Message, - LastCommitAuthor: latestCommit.AuthorName, + GitRepoHttpsCloneUrl: sc.Git.RepositoryCloneUrl, + GitRepoName: sc.RepoName, + GitProvider: sc.GitProvider.String(), + GitProject: gitProject, + BranchName: scannedBranch, + LastCommitUrl: latestCommit.Url, + LastCommitHash: latestCommit.Hash, + LastCommitMessage: latestCommit.Message, + LastCommitAuthor: latestCommit.AuthorName, } return } From 6444d61f9c7b7ea8ce2b3bebd9ac67270620b953 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 26 Nov 2024 10:23:27 +0200 Subject: [PATCH 05/34] update --- go.mod | 2 +- go.sum | 4 ++-- scanpullrequest/scanpullrequest.go | 15 +++++++++++---- scanpullrequest/scanpullrequest_test.go | 14 +++++++------- scanrepository/scanrepository.go | 12 +++++++----- utils/scandetails.go | 21 ++++++++++++++++++--- utils/scandetails_test.go | 23 ++++++++++++++++++++--- 7 files changed, 66 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 4c69b1c3c..2b632c5c6 100644 --- a/go.mod +++ b/go.mod @@ -123,7 +123,7 @@ require ( ) // eranturgeman:jas-violations-support -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241125121334-069dbae0f2e9 +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126081121-8231b90c8226 // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc diff --git a/go.sum b/go.sum index ba809620a..64f7156ce 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241125121334-069dbae0f2e9 h1:8Efm+GIQvJI9l4Kqq9WQrHZ+/fu7nfR3SwS6P2gYBgU= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241125121334-069dbae0f2e9/go.mod h1:MNeAR2B+YdXgcuGMcTTGJMpUWkVV+meVDvd8Pmh20TU= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126081121-8231b90c8226 h1:CSe0Nn3hORC8bmB4fmtsdUvi2d0JU+ZD6KUnKvlb9q4= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126081121-8231b90c8226/go.mod h1:MNeAR2B+YdXgcuGMcTTGJMpUWkVV+meVDvd8Pmh20TU= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 3e57c38b8..7f9b3ceea 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -26,9 +26,7 @@ const ( analyticsScanPrScanType = "PR" ) -type ScanPullRequestCmd struct { - XrayVersion string -} +type ScanPullRequestCmd struct{} // Run ScanPullRequest method only works for a single repository scan. // Therefore, the first repository config represents the repository on which Frogbot runs, and it is the only one that matters. @@ -128,16 +126,25 @@ func toFailTaskStatus(repo *utils.Repository, issues *utils.IssuesCollection) bo // Downloads Pull Requests branches code and audits them func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) (issuesCollection *utils.IssuesCollection, err error) { + + repositoryInfo, err := client.GetRepositoryInfo(context.Background(), repoConfig.RepoOwner, repoConfig.RepoName) + if err != nil { + return + } + repoConfig.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP + scanDetails := utils.NewScanDetails(client, &repoConfig.Server, &repoConfig.Git). - SetXrayGraphScanParams(repoConfig.Watches, repoConfig.JFrogProjectKey, len(repoConfig.AllowedLicenses) > 0). + SetXrayGraphScanParams(repositoryInfo.CloneInfo.HTTP, repoConfig.Watches, repoConfig.JFrogProjectKey, len(repoConfig.AllowedLicenses) > 0). SetFixableOnly(repoConfig.FixableOnly). SetFailOnInstallationErrors(*repoConfig.FailOnSecurityIssues). SetConfigProfile(repoConfig.ConfigProfile). SetSkipAutoInstall(repoConfig.SkipAutoInstall). + SetXscGitInfoContext(repoConfig.PullRequestDetails.Source.Name, repoConfig.Project, client). SetDisableJas(repoConfig.DisableJas) if scanDetails, err = scanDetails.SetMinSeverity(repoConfig.MinSeverity); err != nil { return } + scanDetails.XrayVersion = repoConfig.XrayVersion scanDetails.XscVersion = repoConfig.XscVersion diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index 2868fcd74..e9118dd27 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -62,7 +62,7 @@ func scaToDummySimpleJsonResults(response services.ScanResponse, applicable bool convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true, AllowedLicenses: allowedLicenses}) jasResults := &results.JasScansResults{} if applicable { - jasResults.JasVulnerabilities.ApplicabilityScanResults = append(jasResults.JasVulnerabilities.ApplicabilityScanResults, sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2023-4321", ""))) + jasResults.ApplicabilityScanResults = append(jasResults.ApplicabilityScanResults, sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2023-4321", ""))) } cmdResults := &results.SecurityCommandResults{EntitledForJas: applicable, Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "dummy"}, @@ -455,13 +455,13 @@ func TestGetAllIssues(t *testing.T) { }}, }, JasResults: &results.JasScansResults{ + ApplicabilityScanResults: []*sarif.Run{ + sarifutils.CreateRunWithDummyResults( + sarifutils.CreateDummyPassingResult("applic_CVE-2023-3122"), + sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2022-2122", ""), + ), + }, JasVulnerabilities: &results.JasScanResults{ - ApplicabilityScanResults: []*sarif.Run{ - sarifutils.CreateRunWithDummyResults( - sarifutils.CreateDummyPassingResult("applic_CVE-2023-3122"), - sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2022-2122", ""), - ), - }, IacScanResults: []*sarif.Run{ sarifutils.CreateRunWithDummyResults( sarifutils.CreateResultWithLocations("Missing auto upgrade was detected", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index e988b8229..5886ae4df 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -122,9 +122,14 @@ func (cfp *ScanRepositoryCmd) scanAndFixBranch(repository *utils.Repository) (er } func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Repository, client vcsclient.VcsClient) (err error) { + repositoryInfo, err := client.GetRepositoryInfo(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName) + if err != nil { + return + } + // Set the scan details cfp.scanDetails = utils.NewScanDetails(client, &repository.Server, &repository.Git). - SetXrayGraphScanParams(repository.Watches, repository.JFrogProjectKey, len(repository.AllowedLicenses) > 0). + SetXrayGraphScanParams(repositoryInfo.CloneInfo.HTTP, repository.Watches, repository.JFrogProjectKey, len(repository.AllowedLicenses) > 0). SetFailOnInstallationErrors(*repository.FailOnSecurityIssues). SetFixableOnly(repository.FixableOnly). SetSkipAutoInstall(repository.SkipAutoInstall). @@ -137,10 +142,7 @@ func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Reposito if cfp.scanDetails, err = cfp.scanDetails.SetMinSeverity(repository.MinSeverity); err != nil { return } - repositoryInfo, err := client.GetRepositoryInfo(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName) - if err != nil { - return - } + cfp.scanDetails.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP // Set the flag for aggregating fixes to generate a unified pull request for fixing vulnerabilities cfp.aggregateFixes = repository.Git.AggregateFixes diff --git a/utils/scandetails.go b/utils/scandetails.go index 0af8e564a..2a77fdc73 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -57,8 +57,8 @@ func (sc *ScanDetails) SetProject(project *Project) *ScanDetails { return sc } -func (sc *ScanDetails) SetXrayGraphScanParams(watches []string, jfrogProjectKey string, includeLicenses bool) *ScanDetails { - sc.XrayGraphScanParams = createXrayScanParams(watches, jfrogProjectKey, includeLicenses) +func (sc *ScanDetails) SetXrayGraphScanParams(httpCloneUrl string, watches []string, jfrogProjectKey string, includeLicenses bool) *ScanDetails { + sc.XrayGraphScanParams = createXrayScanParams(httpCloneUrl, watches, jfrogProjectKey, includeLicenses) return sc } @@ -157,11 +157,25 @@ func (sc *ScanDetails) HasViolationContext() bool { return sc.ProjectKey != "" || len(sc.Watches) > 0 || sc.RepoPath != "" } -func createXrayScanParams(watches []string, project string, includeLicenses bool) (params *services.XrayGraphScanParams) { +func createXrayScanParams(httpCloneUrl string, watches []string, project string, includeLicenses bool) (params *services.XrayGraphScanParams) { params = &services.XrayGraphScanParams{ ScanType: services.Dependency, IncludeLicenses: includeLicenses, } + if len(httpCloneUrl) > 0 && params.XscGitInfoContext == nil { + // TODO: control with other var, this is always true. + if project != "" { + log.Warn("Using git URL as violation context, project key will be ignored.") + } + params.ProjectKey = "" + + params.Watches = watches + + params.XscGitInfoContext = &services.XscGitInfoContext{ + GitRepoHttpsCloneUrl: httpCloneUrl, + } + return + } if len(watches) > 0 { params.Watches = watches return @@ -199,6 +213,7 @@ func (sc *ScanDetails) RunInstallAndAudit(workDirs ...string) (auditResults *res SetGraphBasicParams(auditBasicParams). SetCommonGraphScanParams(sc.CreateCommonGraphScanParams()). SetConfigProfile(sc.configProfile). + SetGitInfoContext(sc.XscGitInfoContext). SetMultiScanId(sc.MultiScanId). SetStartTime(sc.StartTime) diff --git a/utils/scandetails_test.go b/utils/scandetails_test.go index 7791fc7d1..f685c7f9e 100644 --- a/utils/scandetails_test.go +++ b/utils/scandetails_test.go @@ -9,25 +9,42 @@ import ( func TestCreateXrayScanParams(t *testing.T) { // Project scanDetails := &ScanDetails{} - scanDetails.SetXrayGraphScanParams(nil, "", false) + scanDetails.SetXrayGraphScanParams("", nil, "", false) assert.Empty(t, scanDetails.Watches) assert.Equal(t, "", scanDetails.ProjectKey) assert.True(t, scanDetails.IncludeVulnerabilities) assert.False(t, scanDetails.IncludeLicenses) // Watches - scanDetails.SetXrayGraphScanParams([]string{"watch-1", "watch-2"}, "", false) + scanDetails.SetXrayGraphScanParams("", []string{"watch-1", "watch-2"}, "", false) assert.Equal(t, []string{"watch-1", "watch-2"}, scanDetails.Watches) assert.Equal(t, "", scanDetails.ProjectKey) assert.False(t, scanDetails.IncludeVulnerabilities) assert.False(t, scanDetails.IncludeLicenses) // Project - scanDetails.SetXrayGraphScanParams(nil, "project", true) + scanDetails.SetXrayGraphScanParams("", nil, "project", true) assert.Empty(t, scanDetails.Watches) assert.Equal(t, "project", scanDetails.ProjectKey) assert.False(t, scanDetails.IncludeVulnerabilities) assert.True(t, scanDetails.IncludeLicenses) + + // GitInfoContext + scanDetails.SetXrayGraphScanParams("http://localhost:8080/my-user/my-project.git", nil, "", false) + assert.Empty(t, scanDetails.Watches) + assert.Equal(t, "", scanDetails.ProjectKey) + assert.False(t, scanDetails.IncludeVulnerabilities) + assert.False(t, scanDetails.IncludeLicenses) + assert.NotNil(t, scanDetails.XscGitInfoContext) + assert.Equal(t, "http://localhost:8080/my-user/my-project.git", scanDetails.XscGitInfoContext.GitRepoHttpsCloneUrl) + + // Vulnerabilities + scanDetails.SetXrayGraphScanParams("", nil, "", true) + assert.Empty(t, scanDetails.Watches) + assert.Equal(t, "", scanDetails.ProjectKey) + assert.True(t, scanDetails.IncludeVulnerabilities) + assert.True(t, scanDetails.IncludeLicenses) + assert.Nil(t, scanDetails.XscGitInfoContext) } func TestGetFullPathWorkingDirs(t *testing.T) { From 21c5caff489ff8c696d036691e2908b164173c1d Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 26 Nov 2024 10:34:09 +0200 Subject: [PATCH 06/34] update security --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2b632c5c6..47e38c753 100644 --- a/go.mod +++ b/go.mod @@ -123,7 +123,7 @@ require ( ) // eranturgeman:jas-violations-support -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126081121-8231b90c8226 +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126082842-952d30d9681a // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc diff --git a/go.sum b/go.sum index 64f7156ce..fd75d60ef 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126081121-8231b90c8226 h1:CSe0Nn3hORC8bmB4fmtsdUvi2d0JU+ZD6KUnKvlb9q4= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126081121-8231b90c8226/go.mod h1:MNeAR2B+YdXgcuGMcTTGJMpUWkVV+meVDvd8Pmh20TU= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126082842-952d30d9681a h1:rx1NgDDjGjSVagqA+mWw05d8AAe5igQ01SDHvFwWq2s= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126082842-952d30d9681a/go.mod h1:MNeAR2B+YdXgcuGMcTTGJMpUWkVV+meVDvd8Pmh20TU= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= From 5c1ea97b4667152b7b56510c6849444342f1a3cd Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 26 Nov 2024 16:27:09 +0200 Subject: [PATCH 07/34] continue for demo --- go.mod | 3 ++- go.sum | 4 ++-- scanpullrequest/scanpullrequest.go | 15 +++++++-------- scanpullrequest/scanpullrequest_test.go | 2 +- scanrepository/scanrepository.go | 10 +++++----- scanrepository/scanrepository_test.go | 2 +- utils/issuescollection.go | 8 ++++++++ utils/scandetails.go | 2 +- utils/utils.go | 8 ++++---- 9 files changed, 31 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 47e38c753..20413a14f 100644 --- a/go.mod +++ b/go.mod @@ -123,7 +123,8 @@ require ( ) // eranturgeman:jas-violations-support -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126082842-952d30d9681a +// replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126101846-e580111b0f4b // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc diff --git a/go.sum b/go.sum index fd75d60ef..a412706fb 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126082842-952d30d9681a h1:rx1NgDDjGjSVagqA+mWw05d8AAe5igQ01SDHvFwWq2s= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126082842-952d30d9681a/go.mod h1:MNeAR2B+YdXgcuGMcTTGJMpUWkVV+meVDvd8Pmh20TU= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126101846-e580111b0f4b h1:DkRbgDwBKmJjUDoF+SEhn30SkURWBgMRT7pYuy0B6ew= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126101846-e580111b0f4b/go.mod h1:MNeAR2B+YdXgcuGMcTTGJMpUWkVV+meVDvd8Pmh20TU= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 7f9b3ceea..8b309efac 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -121,7 +121,7 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er func toFailTaskStatus(repo *utils.Repository, issues *utils.IssuesCollection) bool { failFlagSet := repo.FailOnSecurityIssues != nil && *repo.FailOnSecurityIssues - return failFlagSet && issues.PresentableIssuesExists() + return failFlagSet && (issues.PresentableIssuesExists() || issues.ViolationsExists()) } // Downloads Pull Requests branches code and audits them @@ -195,10 +195,9 @@ func auditPullRequestInProject(repoConfig *utils.Repository, scanDetails *utils. // Set JAS output flags repoConfig.OutputWriter.SetJasOutputFlags(sourceResults.EntitledForJas, sourceResults.HasJasScansResults(jasutils.Applicability)) - // Get all issues that exist in the source branch if repoConfig.IncludeAllVulnerabilities { - if auditIssues, err = getAllIssues(sourceResults, repoConfig.AllowedLicenses, scanDetails.HasViolationContext()); err != nil { + if auditIssues, err = getAllIssues(sourceResults, repoConfig.AllowedLicenses, scanDetails.IncludeVulnerabilities, scanDetails.HasViolationContext()); err != nil { return } utils.ConvertSarifPathsToRelative(auditIssues, sourceBranchWd) @@ -235,7 +234,7 @@ func auditTargetBranch(repoConfig *utils.Repository, scanDetails *utils.ScanDeta } // Get newly added issues - newIssues, err = getNewlyAddedIssues(targetResults, sourceScanResults, repoConfig.AllowedLicenses, scanDetails.HasViolationContext()) + newIssues, err = getNewlyAddedIssues(targetResults, sourceScanResults, repoConfig.AllowedLicenses, scanDetails.IncludeVulnerabilities, scanDetails.HasViolationContext()) return } @@ -297,10 +296,10 @@ func checkoutToCommitAtTempWorkingDir(scanDetails *utils.ScanDetails, commitHash return gitManager.CheckoutToHash(commitHash, wd) } -func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses []string, hasViolationContext bool) (*utils.IssuesCollection, error) { +func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*utils.IssuesCollection, error) { log.Info("Frogbot is configured to show all vulnerabilities") simpleJsonResults, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{ - IncludeVulnerabilities: true, + IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext, AllowedLicenses: allowedLicenses, IncludeLicenses: true, @@ -326,9 +325,9 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] } // Returns all the issues found in the source branch that didn't exist in the target branch. -func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandResults, allowedLicenses []string, hasViolationContext bool) (*utils.IssuesCollection, error) { +func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*utils.IssuesCollection, error) { var err error - convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: hasViolationContext, IncludeLicenses: len(allowedLicenses) > 0, AllowedLicenses: allowedLicenses, SimplifiedOutput: true}) + convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext, IncludeLicenses: len(allowedLicenses) > 0, AllowedLicenses: allowedLicenses, SimplifiedOutput: true}) simpleJsonSource, err := convertor.ConvertToSimpleJson(sourceResults) if err != nil { return nil, err diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index e9118dd27..2904f3df6 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -572,7 +572,7 @@ func TestGetAllIssues(t *testing.T) { }, } - issuesRows, err := getAllIssues(auditResults, allowedLicenses, false) + issuesRows, err := getAllIssues(auditResults, allowedLicenses, true, false) if assert.NoError(t, err) { assert.ElementsMatch(t, expectedOutput.ScaVulnerabilities, issuesRows.ScaVulnerabilities) diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index 5886ae4df..7297c731f 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -177,7 +177,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) (i } continue } - if summary, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: cfp.scanDetails.HasViolationContext()}).ConvertToSummary(scanResults); err != nil { + if summary, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: cfp.scanDetails.IncludeVulnerabilities, HasViolationContext: cfp.scanDetails.HasViolationContext()}).ConvertToSummary(scanResults); err != nil { return totalFindings, err } else { findingCount := summary.GetTotalViolations() @@ -191,7 +191,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) (i // Uploads Sarif results to GitHub in order to view the scan in the code scanning UI // Currently available on GitHub only and JFrog Advance Security package // Only if Jas entitlement is available - if err = utils.UploadSarifResultsToGithubSecurityTab(scanResults, repository, cfp.scanDetails.BaseBranch(), cfp.scanDetails.Client(), cfp.scanDetails.HasViolationContext()); err != nil { + if err = utils.UploadSarifResultsToGithubSecurityTab(scanResults, repository, cfp.scanDetails.BaseBranch(), cfp.scanDetails.Client(), cfp.scanDetails.IncludeVulnerabilities, cfp.scanDetails.HasViolationContext()); err != nil { log.Warn(err) } } @@ -233,7 +233,7 @@ func (cfp *ScanRepositoryCmd) scan(currentWorkingDir string) (*results.SecurityC } func (cfp *ScanRepositoryCmd) getVulnerabilitiesMap(scanResults *results.SecurityCommandResults) (map[string]*utils.VulnerabilityDetails, error) { - vulnerabilitiesMap, err := cfp.createVulnerabilitiesMap(scanResults, cfp.scanDetails.HasViolationContext()) + vulnerabilitiesMap, err := cfp.createVulnerabilitiesMap(scanResults, cfp.scanDetails.IncludeVulnerabilities, cfp.scanDetails.HasViolationContext()) if err != nil { return nil, err } @@ -565,9 +565,9 @@ func (cfp *ScanRepositoryCmd) cloneRepositoryOrUseLocalAndCheckoutToBranch() (te } // Create a vulnerabilities map - a map with 'impacted package' as a key and all the necessary information of this vulnerability as value. -func (cfp *ScanRepositoryCmd) createVulnerabilitiesMap(scanResults *results.SecurityCommandResults, hasViolationContext bool) (map[string]*utils.VulnerabilityDetails, error) { +func (cfp *ScanRepositoryCmd) createVulnerabilitiesMap(scanResults *results.SecurityCommandResults, includeVulnerabilities, hasViolationContext bool) (map[string]*utils.VulnerabilityDetails, error) { vulnerabilitiesMap := map[string]*utils.VulnerabilityDetails{} - simpleJsonResult, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: hasViolationContext}).ConvertToSimpleJson(scanResults) + simpleJsonResult, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext}).ConvertToSimpleJson(scanResults) if err != nil { return nil, err } diff --git a/scanrepository/scanrepository_test.go b/scanrepository/scanrepository_test.go index f4e8d37dd..c922e6e3b 100644 --- a/scanrepository/scanrepository_test.go +++ b/scanrepository/scanrepository_test.go @@ -594,7 +594,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - fixVersionsMap, err := cfp.createVulnerabilitiesMap(testCase.scanResults, true) + fixVersionsMap, err := cfp.createVulnerabilitiesMap(testCase.scanResults, true, true) assert.NoError(t, err) for name, expectedVuln := range testCase.expectedMap { actualVuln, exists := fixVersionsMap[name] diff --git a/utils/issuescollection.go b/utils/issuescollection.go index 10487835b..56a42fd39 100644 --- a/utils/issuescollection.go +++ b/utils/issuescollection.go @@ -79,6 +79,10 @@ func (ic *IssuesCollection) PresentableIssuesExists() bool { return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.LicensesViolationsExists() || ic.SastIssuesExists() } +func (ic *IssuesCollection) ViolationsExists() bool { + return len(ic.ScaViolations) > 0 || len(ic.IacViolations) > 0 || len(ic.SecretsViolations) > 0 || len(ic.SastViolations) > 0 || len(ic.LicensesViolations) > 0 +} + func (ic *IssuesCollection) Append(issues *IssuesCollection) { if issues == nil { return @@ -90,24 +94,28 @@ func (ic *IssuesCollection) Append(issues *IssuesCollection) { ic.ScaViolations = append(ic.ScaViolations, issues.ScaViolations...) } + if len(issues.SecretsVulnerabilities) > 0 { ic.SecretsVulnerabilities = append(ic.SecretsVulnerabilities, issues.SecretsVulnerabilities...) } if len(issues.SecretsViolations) > 0 { ic.SecretsViolations = append(ic.SecretsViolations, issues.SecretsViolations...) } + if len(issues.SastVulnerabilities) > 0 { ic.SastVulnerabilities = append(ic.SastVulnerabilities, issues.SastVulnerabilities...) } if len(issues.SastViolations) > 0 { ic.SastViolations = append(ic.SastViolations, issues.SastViolations...) } + if len(issues.IacVulnerabilities) > 0 { ic.IacVulnerabilities = append(ic.IacVulnerabilities, issues.IacVulnerabilities...) } if len(issues.IacViolations) > 0 { ic.IacViolations = append(ic.IacViolations, issues.IacViolations...) } + if len(issues.LicensesViolations) > 0 { ic.LicensesViolations = append(ic.LicensesViolations, issues.LicensesViolations...) } diff --git a/utils/scandetails.go b/utils/scandetails.go index 2a77fdc73..78a6cb565 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -154,7 +154,7 @@ func (sc *ScanDetails) CreateCommonGraphScanParams() *scangraph.CommonGraphScanP } func (sc *ScanDetails) HasViolationContext() bool { - return sc.ProjectKey != "" || len(sc.Watches) > 0 || sc.RepoPath != "" + return sc.ProjectKey != "" || len(sc.Watches) > 0 || sc.RepoPath != "" || (sc.XscGitInfoContext != nil && sc.XscGitInfoContext.GitRepoHttpsCloneUrl != "") } func createXrayScanParams(httpCloneUrl string, watches []string, project string, includeLicenses bool) (params *services.XrayGraphScanParams) { diff --git a/utils/utils.go b/utils/utils.go index de88591e7..1f16d0d01 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -222,8 +222,8 @@ func VulnerabilityDetailsToMD5Hash(vulnerabilities ...formats.VulnerabilityOrVio return hex.EncodeToString(hash.Sum(nil)), nil } -func UploadSarifResultsToGithubSecurityTab(scanResults *results.SecurityCommandResults, repo *Repository, branch string, client vcsclient.VcsClient, hasViolationContext bool) error { - report, err := GenerateFrogbotSarifReport(scanResults, scanResults.HasMultipleTargets(), hasViolationContext, repo.AllowedLicenses) +func UploadSarifResultsToGithubSecurityTab(scanResults *results.SecurityCommandResults, repo *Repository, branch string, client vcsclient.VcsClient, includeVulnerabilities, hasViolationContext bool) error { + report, err := GenerateFrogbotSarifReport(scanResults, scanResults.HasMultipleTargets(), includeVulnerabilities, hasViolationContext, repo.AllowedLicenses) if err != nil { return err } @@ -235,9 +235,9 @@ func UploadSarifResultsToGithubSecurityTab(scanResults *results.SecurityCommandR return nil } -func GenerateFrogbotSarifReport(extendedResults *results.SecurityCommandResults, isMultipleRoots, hasViolationContext bool, allowedLicenses []string) (string, error) { +func GenerateFrogbotSarifReport(extendedResults *results.SecurityCommandResults, isMultipleRoots, includeVulnerabilities, hasViolationContext bool, allowedLicenses []string) (string, error) { convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{ - IncludeVulnerabilities: true, + IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext, IsMultipleRoots: &isMultipleRoots, AllowedLicenses: allowedLicenses, From e97b072532a8ceb01e904e976717c5cac30b4ad5 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 26 Nov 2024 18:24:34 +0200 Subject: [PATCH 08/34] dont show ok banner if violations exists --- utils/comment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/comment.go b/utils/comment.go index f43c9ca44..358680d4b 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -106,7 +106,7 @@ func GenerateFixPullRequestDetails(vulnerabilities []formats.VulnerabilityOrViol } func generatePullRequestSummaryComment(issuesCollection *IssuesCollection, writer outputwriter.OutputWriter) []string { - if !issuesCollection.PresentableIssuesExists() { + if !issuesCollection.PresentableIssuesExists() && !issuesCollection.ViolationsExists() { return outputwriter.GetPRSummaryContent([]string{}, false, true, writer) } From 1b3a06c99cae08513ac06356fa26655ba5a8a168 Mon Sep 17 00:00:00 2001 From: attiasas Date: Sun, 1 Dec 2024 14:28:57 +0200 Subject: [PATCH 09/34] start edit summary table --- utils/outputwriter/outputcontent.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 8c19e755f..7fc8b1ba2 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -134,6 +134,23 @@ func vulnerabilitiesSummaryContent(vulnerabilities []formats.VulnerabilityOrViol return contentBuilder.String() } +func getIssuesSummaryTable(writer OutputWriter) string { + // Construct table + columns := []string{"Severity/Risk", "ID", "Type"} + if writer.IsShowingCaColumn() { + columns = append(columns, "Contextual Analysis") + } + columns = append(columns, "Direct Dependency", "Impacted Dependency", "File Path", "Line") + table := NewMarkdownTable(columns...).SetDelimiter(writer.Separator()) + if _, ok := writer.(*SimplifiedOutput); ok { + // The values in this cell can be potentially large, since SimplifiedOutput does not support tags, we need to show each value in a separate row. + // It means that the first row will show the full details, and the following rows will show only the direct dependency. + // It makes it easier to read the table and less crowded with text in a single cell that could be potentially large. + table.GetColumnInfo("Direct Dependency").ColumnType = MultiRowColumn + } + +} + func getVulnerabilitiesSummaryTable(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { // Construct table columns := []string{"SEVERITY"} From d4ce51f9319a30324d7e23802d2fd91d938cb1ed Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 3 Dec 2024 18:30:33 +0200 Subject: [PATCH 10/34] Add LicenseViolationRow, rename IssuesCollection, start impl violation content --- go.mod | 14 +- go.sum | 16 +- scanpullrequest/scanpullrequest.go | 31 +- scanpullrequest/scanpullrequest_test.go | 21 +- utils/comment.go | 9 +- utils/comment_test.go | 7 +- utils/{ => issues}/issuescollection.go | 111 ++++- utils/{ => issues}/issuescollection_test.go | 4 +- utils/outputwriter/markdowntable.go | 14 +- utils/outputwriter/outputcontent.go | 470 +++++++++++++++++++- utils/outputwriter/outputcontent_test.go | 55 +-- utils/outputwriter/outputwriter.go | 4 + utils/utils.go | 3 +- 13 files changed, 637 insertions(+), 122 deletions(-) rename utils/{ => issues}/issuescollection.go (50%) rename utils/{ => issues}/issuescollection_test.go (95%) diff --git a/go.mod b/go.mod index 20413a14f..b47b6214f 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,17 @@ module github.com/jfrog/frogbot/v2 -go 1.22.9 - -toolchain go1.23.3 +go 1.23.3 require ( github.com/go-git/go-git/v5 v5.12.0 github.com/golang/mock v1.6.0 github.com/google/go-github/v45 v45.2.0 - github.com/jfrog/build-info-go v1.10.5 + github.com/jfrog/build-info-go v1.10.6 github.com/jfrog/froggit-go v1.16.2 github.com/jfrog/gofrog v1.7.6 - github.com/jfrog/jfrog-cli-core/v2 v2.56.7 + github.com/jfrog/jfrog-cli-core/v2 v2.57.0 github.com/jfrog/jfrog-cli-security v1.13.1 - github.com/jfrog/jfrog-client-go v1.47.6 + github.com/jfrog/jfrog-client-go v1.48.2 github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/owenrumney/go-sarif/v2 v2.3.1 github.com/stretchr/testify v1.9.0 @@ -124,7 +122,7 @@ require ( // eranturgeman:jas-violations-support // replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126101846-e580111b0f4b +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241203111641-2e4a959b3031 // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc @@ -133,6 +131,6 @@ replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev // attiasas:add_repo_context_scan_graph -replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241125121151-c243bb1fe709 +replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go dev diff --git a/go.sum b/go.sum index a412706fb..af2363ef5 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/attiasas/jfrog-client-go v0.0.0-20241125121151-c243bb1fe709 h1:kgtTR4QpGceQWIvAkWESToWV7xW4RXLbBUTcuPZwB+4= -github.com/attiasas/jfrog-client-go v0.0.0-20241125121151-c243bb1fe709/go.mod h1:1a7bmQHkRmPEza9wva2+WVrYzrGbosrMymq57kyG5gU= +github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a h1:47orWZJqdB4YIiqnYd0ysEjvqXiwy3eadwKkHo6s1qg= +github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a/go.mod h1:1a7bmQHkRmPEza9wva2+WVrYzrGbosrMymq57kyG5gU= github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= @@ -59,8 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126101846-e580111b0f4b h1:DkRbgDwBKmJjUDoF+SEhn30SkURWBgMRT7pYuy0B6ew= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241126101846-e580111b0f4b/go.mod h1:MNeAR2B+YdXgcuGMcTTGJMpUWkVV+meVDvd8Pmh20TU= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241203111641-2e4a959b3031 h1:M+eMYoyLanh9FV4ST2ljiuHbqafysEryHVUtg4PCfYE= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241203111641-2e4a959b3031/go.mod h1:c4GycFVqXqYOjI/1FdJ1lUf//kqQBGEzds1iSn73OaM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= @@ -127,16 +127,16 @@ github.com/jedib0t/go-pretty/v6 v6.6.1 h1:iJ65Xjb680rHcikRj6DSIbzCex2huitmc7bDtx github.com/jedib0t/go-pretty/v6 v6.6.1/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= -github.com/jfrog/build-info-go v1.10.5 h1:cW03JlPlKv7RMUU896uLUxyLWXAmCgR5Y5QX0fwgz0Q= -github.com/jfrog/build-info-go v1.10.5/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= +github.com/jfrog/build-info-go v1.10.6 h1:zH1ZhXlVfi5DlFyunygHjrdOcnv5qxfeLqmsfD4+lc4= +github.com/jfrog/build-info-go v1.10.6/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= github.com/jfrog/froggit-go v1.16.2 h1:F//S83iXH14qsCwYzv0zB2JtjS2pJVEsUoEmYA+37dQ= github.com/jfrog/froggit-go v1.16.2/go.mod h1:5VpdQfAcbuyFl9x/x8HGm7kVk719kEtW/8YJFvKcHPA= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.56.7 h1:pB4ronzVk60k/lf9bUL9HxBZ8PbMW6LhbIFld9NXNNc= -github.com/jfrog/jfrog-cli-core/v2 v2.56.7/go.mod h1:puLwWcnXYCJqUOvhscXRJiKNzPdj0adP+zadKy6A/gU= +github.com/jfrog/jfrog-cli-core/v2 v2.57.0 h1:3ON0J6Sjc2+4HZrzh4eSbdciXx3sJsJUIJ3TPQXh/5c= +github.com/jfrog/jfrog-cli-core/v2 v2.57.0/go.mod h1:SThaC/fniC96oN8YgCsHjvOxp5rBM7IppuIybn1oxT0= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 8b309efac..e109e1a4e 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -7,6 +7,7 @@ import ( "os" "github.com/jfrog/frogbot/v2/utils" + "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/froggit-go/vcsutils" "github.com/jfrog/gofrog/datastructures" @@ -119,13 +120,13 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er return } -func toFailTaskStatus(repo *utils.Repository, issues *utils.IssuesCollection) bool { +func toFailTaskStatus(repo *utils.Repository, issues *issues.ScansIssuesCollection) bool { failFlagSet := repo.FailOnSecurityIssues != nil && *repo.FailOnSecurityIssues return failFlagSet && (issues.PresentableIssuesExists() || issues.ViolationsExists()) } // Downloads Pull Requests branches code and audits them -func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) (issuesCollection *utils.IssuesCollection, err error) { +func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) (issuesCollection *issues.ScansIssuesCollection, err error) { repositoryInfo, err := client.GetRepositoryInfo(context.Background(), repoConfig.RepoOwner, repoConfig.RepoName) if err != nil { @@ -161,10 +162,10 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) } }() - issuesCollection = &utils.IssuesCollection{} + issuesCollection = &issues.ScansIssuesCollection{} for i := range repoConfig.Projects { scanDetails.SetProject(&repoConfig.Projects[i]) - var projectIssues *utils.IssuesCollection + var projectIssues *issues.ScansIssuesCollection if projectIssues, err = auditPullRequestInProject(repoConfig, scanDetails); err != nil { return } @@ -173,7 +174,7 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) return } -func auditPullRequestInProject(repoConfig *utils.Repository, scanDetails *utils.ScanDetails) (auditIssues *utils.IssuesCollection, err error) { +func auditPullRequestInProject(repoConfig *utils.Repository, scanDetails *utils.ScanDetails) (auditIssues *issues.ScansIssuesCollection, err error) { // Download source branch sourcePullRequestInfo := scanDetails.PullRequestDetails.Source sourceBranchWd, cleanupSource, err := utils.DownloadRepoToTempDir(scanDetails.Client(), sourcePullRequestInfo.Owner, sourcePullRequestInfo.Repository, sourcePullRequestInfo.Name) @@ -212,7 +213,7 @@ func auditPullRequestInProject(repoConfig *utils.Repository, scanDetails *utils. return } -func auditTargetBranch(repoConfig *utils.Repository, scanDetails *utils.ScanDetails, sourceScanResults *results.SecurityCommandResults) (newIssues *utils.IssuesCollection, targetBranchWd string, err error) { +func auditTargetBranch(repoConfig *utils.Repository, scanDetails *utils.ScanDetails, sourceScanResults *results.SecurityCommandResults) (newIssues *issues.ScansIssuesCollection, targetBranchWd string, err error) { // Download target branch (if needed) cleanupTarget := func() error { return nil } if !repoConfig.IncludeAllVulnerabilities { @@ -296,7 +297,7 @@ func checkoutToCommitAtTempWorkingDir(scanDetails *utils.ScanDetails, commitHash return gitManager.CheckoutToHash(commitHash, wd) } -func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*utils.IssuesCollection, error) { +func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*issues.ScansIssuesCollection, error) { log.Info("Frogbot is configured to show all vulnerabilities") simpleJsonResults, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{ IncludeVulnerabilities: includeVulnerabilities, @@ -308,7 +309,7 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] if err != nil { return nil, err } - return &utils.IssuesCollection{ + return &issues.ScansIssuesCollection{ ScaVulnerabilities: simpleJsonResults.Vulnerabilities, ScaViolations: simpleJsonResults.SecurityViolations, LicensesViolations: simpleJsonResults.LicensesViolations, @@ -325,7 +326,7 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] } // Returns all the issues found in the source branch that didn't exist in the target branch. -func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*utils.IssuesCollection, error) { +func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*issues.ScansIssuesCollection, error) { var err error convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext, IncludeLicenses: len(allowedLicenses) > 0, AllowedLicenses: allowedLicenses, SimplifiedOutput: true}) simpleJsonSource, err := convertor.ConvertToSimpleJson(sourceResults) @@ -336,7 +337,7 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe if err != nil { return nil, err } - return &utils.IssuesCollection{ + return &issues.ScansIssuesCollection{ ScaVulnerabilities: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities), ScaViolations: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.SecurityViolations, simpleJsonSource.SecurityViolations), IacVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities), @@ -381,14 +382,14 @@ func getUniqueVulnerabilityOrViolationRows(targetRows, sourceRows []formats.Vuln return newRows } -func getUniqueLicenseRows(targetRows, sourceRows []formats.LicenseRow) []formats.LicenseRow { - existingLicenses := make(map[string]formats.LicenseRow) - var newLicenses []formats.LicenseRow +func getUniqueLicenseRows(targetRows, sourceRows []formats.LicenseViolationRow) []formats.LicenseViolationRow { + existingLicenses := make(map[string]formats.LicenseViolationRow) + var newLicenses []formats.LicenseViolationRow for _, row := range targetRows { - existingLicenses[getUniqueLicenseKey(row)] = row + existingLicenses[getUniqueLicenseKey(row.LicenseRow)] = row } for _, row := range sourceRows { - if _, exists := existingLicenses[getUniqueLicenseKey(row)]; !exists { + if _, exists := existingLicenses[getUniqueLicenseKey(row.LicenseRow)]; !exists { newLicenses = append(newLicenses, row) } } diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index 2904f3df6..eb32178a4 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/jfrog/frogbot/v2/utils" + "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/frogbot/v2/utils/outputwriter" "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/froggit-go/vcsutils" @@ -45,7 +46,7 @@ const ( testTargetBranchName = "master" ) -func createScaDiff(t *testing.T, previousScan, currentScan services.ScanResponse, applicable bool, allowedLicenses ...string) (securityViolationsRows []formats.VulnerabilityOrViolationRow, licenseViolations []formats.LicenseRow) { +func createScaDiff(t *testing.T, previousScan, currentScan services.ScanResponse, applicable bool, allowedLicenses ...string) (securityViolationsRows []formats.VulnerabilityOrViolationRow, licenseViolations []formats.LicenseViolationRow) { sourceResults, err := scaToDummySimpleJsonResults(currentScan, applicable, allowedLicenses...) assert.NoError(t, err) targetResults, err := scaToDummySimpleJsonResults(previousScan, applicable, allowedLicenses...) @@ -486,7 +487,7 @@ func TestGetAllIssues(t *testing.T) { }, }, }}} - expectedOutput := &utils.IssuesCollection{ + expectedOutput := &issues.ScansIssuesCollection{ ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { Applicable: "Applicable", @@ -558,15 +559,17 @@ func TestGetAllIssues(t *testing.T) { }, }, }, - LicensesViolations: []formats.LicenseRow{ + LicensesViolations: []formats.LicenseViolationRow{ { - LicenseKey: "Apache-2.0", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: formats.SeverityDetails{ - Severity: "Medium", - SeverityNumValue: 14, + LicenseRow: formats.LicenseRow{ + LicenseKey: "Apache-2.0", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 14, + }, + ImpactedDependencyName: "Dep-1", }, - ImpactedDependencyName: "Dep-1", }, }, }, diff --git a/utils/comment.go b/utils/comment.go index 358680d4b..40e1c0053 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/frogbot/v2/utils/outputwriter" "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/jfrog-cli-security/utils/formats" @@ -30,7 +31,7 @@ const ( commentRemovalErrorMsg = "An error occurred while attempting to remove older Frogbot pull request comments:" ) -func HandlePullRequestCommentsAfterScan(issues *IssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int) (err error) { +func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int) (err error) { if !repo.Params.AvoidPreviousPrCommentsDeletion { // The removal of comments may fail for various reasons, // such as concurrent scanning of pull requests and attempts @@ -105,7 +106,7 @@ func GenerateFixPullRequestDetails(vulnerabilities []formats.VulnerabilityOrViol return } -func generatePullRequestSummaryComment(issuesCollection *IssuesCollection, writer outputwriter.OutputWriter) []string { +func generatePullRequestSummaryComment(issuesCollection *issues.ScansIssuesCollection, writer outputwriter.OutputWriter) []string { if !issuesCollection.PresentableIssuesExists() && !issuesCollection.ViolationsExists() { return outputwriter.GetPRSummaryContent([]string{}, false, true, writer) } @@ -136,7 +137,7 @@ func GetSortedPullRequestComments(client vcsclient.VcsClient, repoOwner, repoNam return pullRequestsComments, nil } -func addReviewComments(repo *Repository, pullRequestID int, client vcsclient.VcsClient, issues *IssuesCollection) (err error) { +func addReviewComments(repo *Repository, pullRequestID int, client vcsclient.VcsClient, issues *issues.ScansIssuesCollection) (err error) { commentsToAdd := getNewReviewComments(repo, issues) if len(commentsToAdd) == 0 { return @@ -183,7 +184,7 @@ func getFrogbotComments(writer outputwriter.OutputWriter, existingComments []vcs return } -func getNewReviewComments(repo *Repository, issues *IssuesCollection) (commentsToAdd []ReviewComment) { +func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection) (commentsToAdd []ReviewComment) { writer := repo.OutputWriter for _, vulnerability := range issues.GetScaIssues() { diff --git a/utils/comment_test.go b/utils/comment_test.go index ac5efab6f..2c1b709f4 100644 --- a/utils/comment_test.go +++ b/utils/comment_test.go @@ -3,6 +3,7 @@ package utils import ( "testing" + "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/frogbot/v2/utils/outputwriter" "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/jfrog-cli-security/utils/formats" @@ -53,12 +54,12 @@ func TestGetNewReviewComments(t *testing.T) { repo := &Repository{OutputWriter: &outputwriter.StandardOutput{}} testCases := []struct { name string - issues *IssuesCollection + issues *issues.ScansIssuesCollection expectedOutput []ReviewComment }{ { name: "No issues for review comments", - issues: &IssuesCollection{ + issues: &issues.ScansIssuesCollection{ ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { Summary: "summary-2", @@ -94,7 +95,7 @@ func TestGetNewReviewComments(t *testing.T) { }, { name: "With issues for review comments", - issues: &IssuesCollection{ + issues: &issues.ScansIssuesCollection{ ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { Summary: "summary-2", diff --git a/utils/issuescollection.go b/utils/issues/issuescollection.go similarity index 50% rename from utils/issuescollection.go rename to utils/issues/issuescollection.go index 56a42fd39..97657e25e 100644 --- a/utils/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -1,48 +1,115 @@ -package utils +package issues import ( "github.com/jfrog/gofrog/datastructures" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/severityutils" ) -type IssuesCollection struct { - ScaVulnerabilities []formats.VulnerabilityOrViolationRow - IacVulnerabilities []formats.SourceCodeRow - SecretsVulnerabilities []formats.SourceCodeRow - SastVulnerabilities []formats.SourceCodeRow +// TODO: after refactor, move this to security-cli as a new formats or remove this and use the existing formats +// Group issues by scan type +type ScansIssuesCollection struct { + LicensesViolations []formats.LicenseViolationRow + ScaVulnerabilities []formats.VulnerabilityOrViolationRow ScaViolations []formats.VulnerabilityOrViolationRow - LicensesViolations []formats.LicenseRow + ScaScanPerformed bool + ScaScanStatus int + + ApplicabilityScanPerformed bool + ApplicabilityScanStatus int + + IacVulnerabilities []formats.SourceCodeRow IacViolations []formats.SourceCodeRow - SecretsViolations []formats.SourceCodeRow - SastViolations []formats.SourceCodeRow + IacScan bool + IacScanStatus int + + SecretsVulnerabilities []formats.SourceCodeRow + SecretsViolations []formats.SourceCodeRow + SecretsScanPerformed bool + SecretsScanStatus int + + SastViolations []formats.SourceCodeRow + SastVulnerabilities []formats.SourceCodeRow + SastScanPerformed bool + SastScanStatus int +} + +func (ic *ScansIssuesCollection) GetTotalViolations() int { + return len(ic.ScaViolations) + len(ic.IacViolations) + len(ic.SecretsViolations) + len(ic.SastViolations) + len(ic.LicensesViolations) +} + +func (ic *ScansIssuesCollection) GetScanDetails(scanType utils.SubScanType, violation bool) map[severityutils.Severity]int { + scanDetails := map[severityutils.Severity]int{} + + if scanType == utils.ScaScan { + if violation { + for _, violation := range ic.ScaViolations { + scanDetails[severityutils.GetSeverity(violation.Severity)]++ + } + } else { + for _, vulnerability := range ic.ScaVulnerabilities { + scanDetails[severityutils.GetSeverity(vulnerability.Severity)]++ + } + } + return scanDetails + } + + jasIssues := []formats.SourceCodeRow{} + switch scanType { + case utils.IacScan: + if violation { + jasIssues = ic.IacViolations + } else { + jasIssues = ic.IacVulnerabilities + } + case utils.SecretsScan: + if violation { + jasIssues = ic.SecretsViolations + } else { + jasIssues = ic.SecretsVulnerabilities + } + case utils.SastScan: + if violation { + jasIssues = ic.SastViolations + } else { + jasIssues = ic.SastVulnerabilities + } + } + + for _, issue := range jasIssues { + scanDetails[severityutils.GetSeverity(issue.Severity)]++ + } + + return scanDetails } -func (ic *IssuesCollection) GetScaIssues() (unique []formats.VulnerabilityOrViolationRow) { +func (ic *ScansIssuesCollection) GetScaIssues() (unique []formats.VulnerabilityOrViolationRow) { return append(ic.ScaVulnerabilities, ic.ScaViolations...) } -func (ic *IssuesCollection) ScaIssuesExists() bool { +func (ic *ScansIssuesCollection) ScaIssuesExists() bool { return len(ic.ScaVulnerabilities) > 0 || len(ic.ScaViolations) > 0 } -func (ic *IssuesCollection) GetUniqueIacIssues() (unique []formats.SourceCodeRow) { +func (ic *ScansIssuesCollection) GetUniqueIacIssues() (unique []formats.SourceCodeRow) { return getUniqueJasIssues(ic.IacVulnerabilities, ic.IacViolations) } -func (ic *IssuesCollection) IacIssuesExists() bool { +func (ic *ScansIssuesCollection) IacIssuesExists() bool { return len(ic.IacVulnerabilities) > 0 || len(ic.IacViolations) > 0 } -func (ic *IssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { +func (ic *ScansIssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { return getUniqueJasIssues(ic.SecretsVulnerabilities, ic.SecretsViolations) } -func (ic *IssuesCollection) SecretsIssuesExists() bool { +func (ic *ScansIssuesCollection) SecretsIssuesExists() bool { return len(ic.SecretsVulnerabilities) > 0 || len(ic.SecretsViolations) > 0 } -func (ic *IssuesCollection) GetUniqueSastIssues() (unique []formats.SourceCodeRow) { +func (ic *ScansIssuesCollection) GetUniqueSastIssues() (unique []formats.SourceCodeRow) { return getUniqueJasIssues(ic.SastVulnerabilities, ic.SastViolations) } @@ -67,23 +134,23 @@ func getUniqueJasIssues(vulnerabilities, violations []formats.SourceCodeRow) (un return } -func (ic *IssuesCollection) SastIssuesExists() bool { +func (ic *ScansIssuesCollection) SastIssuesExists() bool { return len(ic.SastVulnerabilities) > 0 || len(ic.SastViolations) > 0 } -func (ic *IssuesCollection) LicensesViolationsExists() bool { +func (ic *ScansIssuesCollection) LicensesViolationsExists() bool { return len(ic.LicensesViolations) > 0 } -func (ic *IssuesCollection) PresentableIssuesExists() bool { +func (ic *ScansIssuesCollection) PresentableIssuesExists() bool { return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.LicensesViolationsExists() || ic.SastIssuesExists() } -func (ic *IssuesCollection) ViolationsExists() bool { +func (ic *ScansIssuesCollection) ViolationsExists() bool { return len(ic.ScaViolations) > 0 || len(ic.IacViolations) > 0 || len(ic.SecretsViolations) > 0 || len(ic.SastViolations) > 0 || len(ic.LicensesViolations) > 0 } -func (ic *IssuesCollection) Append(issues *IssuesCollection) { +func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { if issues == nil { return } @@ -121,7 +188,7 @@ func (ic *IssuesCollection) Append(issues *IssuesCollection) { } } -func (ic *IssuesCollection) CountIssuesCollectionFindings() int { +func (ic *ScansIssuesCollection) CountIssuesCollectionFindings() int { count := 0 count += len(ic.GetScaIssues()) diff --git a/utils/issuescollection_test.go b/utils/issues/issuescollection_test.go similarity index 95% rename from utils/issuescollection_test.go rename to utils/issues/issuescollection_test.go index 2c4cf14bf..2f93b5b25 100644 --- a/utils/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -1,4 +1,4 @@ -package utils +package issues import ( "testing" @@ -8,7 +8,7 @@ import ( ) func TestCountIssuesCollectionFindings(t *testing.T) { - issuesCollection := IssuesCollection{ + issuesCollection := ScansIssuesCollection{ ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ diff --git a/utils/outputwriter/markdowntable.go b/utils/outputwriter/markdowntable.go index 9ca52fb8b..1ac5c4664 100644 --- a/utils/outputwriter/markdowntable.go +++ b/utils/outputwriter/markdowntable.go @@ -51,11 +51,23 @@ func NewCellData(values ...string) CellData { func NewMarkdownTable(columns ...string) *MarkdownTableBuilder { columnsInfo := []*MarkdownColumn{} for _, column := range columns { - columnsInfo = append(columnsInfo, &MarkdownColumn{Name: column, ColumnType: SeparatorDelimited, DefaultValue: cellDefaultValue}) + columnsInfo = append(columnsInfo, NewMarkdownTableSingleValueColumn(column, cellDefaultValue)) } + return NewMarkdownTableWithColumns(columnsInfo) +} + +func NewMarkdownTableWithColumns(columnsInfo []*MarkdownColumn) *MarkdownTableBuilder { return &MarkdownTableBuilder{columns: columnsInfo, delimiter: simpleSeparator} } +func NewMarkdownTableSingleValueColumn(name, defaultValue string) *MarkdownColumn { + return &MarkdownColumn{Name: name, ColumnType: SeparatorDelimited, DefaultValue: defaultValue} +} + +func NewMarkdownTableMultiValueColumn(name, defaultValue string) *MarkdownColumn { + return &MarkdownColumn{Name: name, ColumnType: MultiRowColumn, DefaultValue: defaultValue} +} + // Set the delimiter that will be used to separate multiple values in a cell. func (t *MarkdownTableBuilder) SetDelimiter(delimiter string) *MarkdownTableBuilder { t.delimiter = delimiter diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 7fc8b1ba2..bab20d4e4 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -4,9 +4,12 @@ import ( "fmt" "strings" + "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/froggit-go/vcsutils" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/results" + "github.com/jfrog/jfrog-cli-security/utils/severityutils" ) const ( @@ -15,11 +18,18 @@ const ( FrogbotDocumentationUrl = "https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot" ReviewCommentId = "FrogbotReviewComment" + scanSummaryTitle = "📊 Scan Summary" + + policyViolationTitle = "🚥 Policy Violations" + securityViolationTitle = "🚨 Security Violations" + licenseViolationTitle = "📜 License Violations" + violationsDetailsSubTitle = "🔖 Details" + vulnerableDependenciesTitle = "📦 Vulnerable Dependencies" vulnerableDependenciesResearchDetailsSubTitle = "🔬 Research Details" - contextualAnalysisTitle = "📦🔍 Contextual Analysis CVE Vulnerability" - iacTitle = "🛠️ Infrastructure as Code Vulnerability" + contextualAnalysisTitle = "📦🔍 Contextual Analysis CVE" + iacTitle = "🛠️ Infrastructure as Code" sastTitle = "🎯 Static Application Security Testing (SAST) Vulnerability" ) @@ -115,40 +125,377 @@ func footer(writer OutputWriter) string { return fmt.Sprintf("%s\n%s", SectionDivider(), writer.MarkInCenter(CommentGeneratedByFrogbot)) } -func VulnerabilitiesContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { - if len(vulnerabilities) == 0 { - return []string{} +// Summary content + +func scanSummaryContent(issues issues.ScansIssuesCollection, violations bool, writer OutputWriter) string { + var contentBuilder strings.Builder + issueType := "vulnerabilities" + if violations { + issueType = "violations" + } + // Title + WriteContent(&contentBuilder, + writer.MarkAsTitle(scanSummaryTitle, 2), + fmt.Sprintf("▶️ Frogbot scanned for %s and found %d issues", issueType, issues.GetTotalViolations()), + ) + // Summary + scaStatus, scaFailed := getSubScanResultStatus(issues.ScaScanPerformed, issues.ScaScanStatus) + applicabilityStatus, _ := getSubScanResultStatus(issues.ApplicabilityScanPerformed, issues.ApplicabilityScanStatus) + sastStatus, sastFailed := getSubScanResultStatus(issues.SastScanPerformed, issues.SastScanStatus) + secretsStatus, secretsFailed := getSubScanResultStatus(issues.SecretsScanPerformed, issues.SecretsScanStatus) + iacStatus, iacFailed := getSubScanResultStatus(issues.IacScan, issues.IacScanStatus) + // Create table, a row for each sub scans summary + table := NewMarkdownTable("Scan Category", "Result", "Security Issues") + table.AddRow(MarkAsBold("Software Composition Analysis"), scaStatus, getScanSecurityIssuesDetails(issues, utils.ScaScan, scaFailed, violations, writer)) + table.AddRow(MarkAsBold("Contextual Analysis"), applicabilityStatus, "") + table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), sastStatus, getScanSecurityIssuesDetails(issues, utils.SastScan, sastFailed, violations, writer)) + table.AddRow(MarkAsBold("Secrets"), secretsStatus, getScanSecurityIssuesDetails(issues, utils.SecretsScan, secretsFailed, violations, writer)) + table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), iacStatus, getScanSecurityIssuesDetails(issues, utils.IacScan, iacFailed, violations, writer)) + WriteContent(&contentBuilder, writer.MarkInCenter(table.Build())) + // link to the scan results in JFrog + // WriteNewLine(&contentBuilder) + // WriteContent(&contentBuilder, writer.MarkInCenter(MarkAsLink("See the results of the scan in JFrog", "an-url"))) + return contentBuilder.String() +} + +func getSubScanResultStatus(scanPerformed bool, statusCode int) (string, bool) { + if !scanPerformed { + return "ℹ️ Not Scanned", false + } + if statusCode == 0 { + return "✅ Done", false + } + return "❌ Failed", true +} + +func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType utils.SubScanType, failed, violation bool, writer OutputWriter) string { + if failed || (scanType == utils.ScaScan && !issues.ScaScanPerformed) || (scanType == utils.SastScan && !issues.SastScanPerformed) || (scanType == utils.SecretsScan && !issues.SecretsScanPerformed) || (scanType == utils.IacScan && !issues.IacScan) { + // Failed/Not scanned, no need to show the details + return "" + } + var severityCountMap map[severityutils.Severity]int + switch scanType { + case utils.ScaScan: + severityCountMap = issues.GetScanDetails(utils.ScaScan, violation) + case utils.SastScan: + severityCountMap = issues.GetScanDetails(utils.SastScan, violation) + case utils.SecretsScan: + severityCountMap = issues.GetScanDetails(utils.SecretsScan, violation) + case utils.IacScan: + severityCountMap = issues.GetScanDetails(utils.IacScan, violation) + } + if len(severityCountMap) == 0 { + // No Issues + return "Not Found" + } + var contentBuilder strings.Builder + WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%d Issues Found", getTotalIssues(severityCountMap)), 3, toSeverityDetails(severityCountMap, writer))) + return contentBuilder.String() +} + +func getTotalIssues(severities map[severityutils.Severity]int) (total int) { + for _, count := range severities { + total += count } - content = append(content, writer.MarkAsTitle(vulnerableDependenciesTitle, 2)) - content = append(content, vulnerabilitiesSummaryContent(vulnerabilities, writer)) - content = append(content, vulnerabilityDetailsContent(vulnerabilities, writer)...) return } -func vulnerabilitiesSummaryContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { +func toSeverityDetails(severities map[severityutils.Severity]int, writer OutputWriter) string { var contentBuilder strings.Builder - WriteContent(&contentBuilder, - writer.MarkAsTitle("✍️ Summary", 3), - writer.MarkInCenter(getVulnerabilitiesSummaryTable(vulnerabilities, writer)), - ) + // Get severities with values and write them sorted (Critical, High, Medium, Low, Unknown) + if count, ok := severities[severityutils.Critical]; ok && count > 0 { + WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.Critical), count, severityutils.Critical.String())) + } + if count, ok := severities[severityutils.High]; ok && count > 0 { + WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.High), count, severityutils.High.String())) + } + if count, ok := severities[severityutils.Medium]; ok && count > 0 { + WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.Medium), count, severityutils.Medium.String())) + } + if count, ok := severities[severityutils.Low]; ok && count > 0 { + WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.Low), count, severityutils.Low.String())) + } + if count, ok := severities[severityutils.Unknown]; ok && count > 0 { + WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.Unknown), count, severityutils.Unknown.String())) + } return contentBuilder.String() } -func getIssuesSummaryTable(writer OutputWriter) string { +// Policy Violations + +// Summary content for the security violations that we can't yet have location on (SCA, License) +func SecurityViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (content []string) { + if issues.GetTotalViolations() == 0 { + return []string{} + } + // Violations Summary + content = append(content, scanSummaryContent(issues, true, writer)) + // Policy Violations Content + policyViolationContent := append(getSecurityViolationsContent(issues, writer), getLicenseViolationsContent(issues, writer)...) + return append(content, ConvertContentToComments(policyViolationContent, writer, getDecoratorWithPolicyViolationTitle(writer))...) +} + +func getDecoratorWithPolicyViolationTitle(writer OutputWriter) func(int, string) string { + return func(commentCount int, content string) string { + contentBuilder := strings.Builder{} + // Decorate each part of the split content with a title as prefix and return the content + WriteContent(&contentBuilder, writer.MarkAsTitle(policyViolationTitle, 2)) + WriteContent(&contentBuilder, content) + return contentBuilder.String() + } +} + +// Security Violations + +func getSecurityViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (content []string) { + content = append(content, getSecurityViolationsSummaryTable(issues.ScaViolations, writer)) + content = append(content, getSecurityViolationsDetailsContent(issues.ScaViolations, writer)...) + return ConvertContentToComments(content, writer, getDecoratorWithSecurityViolationTitle(writer)) +} + +func getDecoratorWithSecurityViolationTitle(writer OutputWriter) func(int, string) string { + return func(commentCount int, content string) string { + contentBuilder := strings.Builder{} + // Decorate each part of the split content with a title as prefix and return the content + WriteContent(&contentBuilder, writer.MarkAsTitle(securityViolationTitle, 3)) + WriteContent(&contentBuilder, content) + return contentBuilder.String() + } +} + +func getSecurityViolationsSummaryTable(violations []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { // Construct table - columns := []string{"Severity/Risk", "ID", "Type"} + columns := []string{"Severity/Risk", "ID"} if writer.IsShowingCaColumn() { columns = append(columns, "Contextual Analysis") } - columns = append(columns, "Direct Dependency", "Impacted Dependency", "File Path", "Line") - table := NewMarkdownTable(columns...).SetDelimiter(writer.Separator()) + table := NewMarkdownTable(append(columns, "Direct Dependencies", "Impacted Dependency", "Watch Name")...).SetDelimiter(writer.Separator()) + if _, ok := writer.(*SimplifiedOutput); ok { + // The values in this cell can be potentially large, since SimplifiedOutput does not support tags, we need to show each value in a separate row. + // It means that the first row will show the full details, and the following rows will show only the direct dependency. + // It makes it easier to read the table and less crowded with text in a single cell that could be potentially large. + table.GetColumnInfo("Direct Dependencies").ColumnType = MultiRowColumn + } + // Construct rows + for _, violation := range violations { + row := []CellData{{writer.FormattedSeverity(violation.Severity, violation.Applicable)}} + if writer.IsShowingCaColumn() { + row = append(row, NewCellData(violation.Applicable)) + } + row = append(row, + getDirectDependenciesCellData("%s:%s", violation.Components), + NewCellData(fmt.Sprintf("%s %s", violation.ImpactedDependencyName, violation.ImpactedDependencyVersion)), + NewCellData(violation.FixedVersions...), + getCveIdsCellData(violation.Cves, violation.IssueId), + ) + table.AddRowWithCellData(row...) + } + return writer.MarkInCenter(table.Build()) +} + +func getImpactedComponentLocationIfDirectDependency(impactedComponent formats.ImpactedDependencyDetails) string { + component := getComponentIfDirect(impactedComponent) + if component != nil && component.Location != nil { + return component.Location.File + } + return "" +} + +func getComponentIfDirect(impactedComponent formats.ImpactedDependencyDetails) (component *formats.ComponentRow) { + for _, c := range impactedComponent.Components { + // Check if the impacted component is a direct dependency + if c.Name == impactedComponent.ImpactedDependencyName && c.Version == impactedComponent.ImpactedDependencyVersion { + return &c + } + } + return +} + +func getSecurityViolationsDetailsContent(violations []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { + if len(violations) == 0 { + return + } + for _, violation := range violations { + if len(violations) == 1 { + content = append(content, getScaSecurityViolationDetails(violation, writer)) + } else { + content = append(content, writer.MarkAsDetails(getViolationDescriptionIdentifier(violation), 5, getScaSecurityViolationDetails(violation, writer))) + } + } + // Split content if it exceeds the size limit and decorate it with title + return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { + contentBuilder := strings.Builder{} + WriteContent(&contentBuilder, writer.MarkAsTitle(violationsDetailsSubTitle, 4)) + WriteContent(&contentBuilder, detailsContent) + return contentBuilder.String() + }) +} + +func getViolationDescriptionIdentifier(violation formats.VulnerabilityOrViolationRow) string { + return fmt.Sprintf(`%s %s %s (%s)`, getVulnerabilityDescriptionIdentifier(violation.Cves, violation.IssueId), violation.ImpactedDependencyName, violation.ImpactedDependencyVersion, violation.Watch) +} + +func getScaSecurityViolationDetails(violation formats.VulnerabilityOrViolationRow, writer OutputWriter) (content string) { + directComponent := []string{} + for _, component := range violation.ImpactedDependencyDetails.Components { + directComponent = append(directComponent, fmt.Sprintf("%s:%s", component.Name, component.Version)) + } + table := getBaseDependencyViolationDetailsTable( + directComponent, + fmt.Sprintf("%s:%s", violation.ImpactedDependencyName, violation.ImpactedDependencyVersion), + violation.Severity, + "Security", + violation.Watch, + violation.Policies, + writer, + ) + if writer.IsShowingCaColumn() { + table.AddRow(MarkAsBold("Contextual Analysis:"), violation.Applicable) + } + if violation.JfrogResearchInformation != nil { + table.AddRow(MarkAsBold("Jfrog Research Severity:"), violation.JfrogResearchInformation.Severity) + } + if len(violation.FixedVersions) > 0 { + table.AddRow(MarkAsBold("Fixed Versions:"), strings.Join(violation.FixedVersions, ", ")) + } + if len(violation.Cves) > 0 { + cvssV3 := []string{} + for _, cve := range violation.Cves { + cvssV3 = append(cvssV3, cve.CvssV3) + } + table.AddRow(MarkAsBold("CVSS V3:"), strings.Join(cvssV3, ", ")) + } + var contentBuilder strings.Builder + WriteContent(&contentBuilder, table.Build(), fmt.Sprintf("%s: %s", MarkAsBold("Description"), violation.Summary)) + return contentBuilder.String() +} + +// License violations + +func getLicenseViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (content []string) { + content = append(content, getLicenseViolationsSummaryTable(issues.LicensesViolations, writer)) + content = append(content, getLicenseViolationsDetailsContent(issues.LicensesViolations, writer)...) + return ConvertContentToComments(content, writer, getDecoratorWithLicenseViolationTitle(writer)) +} + +func getDecoratorWithLicenseViolationTitle(writer OutputWriter) func(int, string) string { + return func(commentCount int, content string) string { + contentBuilder := strings.Builder{} + // Decorate each part of the split content with a title as prefix and return the content + WriteContent(&contentBuilder, writer.MarkAsTitle(licenseViolationTitle, 3)) + WriteContent(&contentBuilder, content) + return contentBuilder.String() + } +} + +func getLicenseViolationsSummaryTable(licenses []formats.LicenseViolationRow, writer OutputWriter) string { + if len(licenses) == 0 { + return "" + } + // Title + var contentBuilder strings.Builder + WriteContent(&contentBuilder, writer.MarkAsTitle("⚖️ Violated Licenses", 2)) + // Content + table := NewMarkdownTable("Severity", "ID", "Direct Dependencies", "Impacted Dependency", "Watch Name").SetDelimiter(writer.Separator()) if _, ok := writer.(*SimplifiedOutput); ok { // The values in this cell can be potentially large, since SimplifiedOutput does not support tags, we need to show each value in a separate row. // It means that the first row will show the full details, and the following rows will show only the direct dependency. // It makes it easier to read the table and less crowded with text in a single cell that could be potentially large. - table.GetColumnInfo("Direct Dependency").ColumnType = MultiRowColumn + table.GetColumnInfo("Direct Dependencies").ColumnType = MultiRowColumn + } + for _, license := range licenses { + table.AddRowWithCellData( + NewCellData(license.Severity), + NewCellData(license.LicenseKey), + getDirectDependenciesCellData("%s %s", license.Components), + NewCellData(fmt.Sprintf("%s %s", license.ImpactedDependencyName, license.ImpactedDependencyVersion)), + NewCellData(license.Watch), + ) + } + WriteContent(&contentBuilder, writer.MarkInCenter(table.Build())) + return contentBuilder.String() +} + +func getLicenseViolationsDetailsContent(licenseViolations []formats.LicenseViolationRow, writer OutputWriter) (content []string) { + if len(licenseViolations) == 0 { + return + } + // for _, violation := range licenseViolations { + // if len(licenseViolations) == 1 { + // content = append(content, getScaLicenseViolationDetails(violation, writer)) + // } else { + // content = append(content, writer.MarkAsDetails(getViolationDescriptionIdentifier(violation), 5, getScaLicenseViolationDetails(violation, writer))) + // } + // } + // Split content if it exceeds the size limit and decorate it with title + return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { + contentBuilder := strings.Builder{} + WriteContent(&contentBuilder, writer.MarkAsTitle(violationsDetailsSubTitle, 4)) + WriteContent(&contentBuilder, detailsContent) + return contentBuilder.String() + }) +} + +func getJasSecurityViolationDetails(severity, violationType, watch string, policies []string, writer OutputWriter) string { + table := getBaseViolationDetailsTable(severity, violationType, watch, policies, writer) + + return table.Build() +} + +func getSecretsSecurityViolationDetails(severity, violationType, watch string, policies []string, writer OutputWriter) string { + // table := getBaseDependencyViolationDetailsTable(severity, violationType, watch, policies, writer) + + // return table.Build() + return "" +} + +func getBaseViolationDetailsTable(severity, violationType, watch string, policies []string, writer OutputWriter) *MarkdownTableBuilder { + noHeaderTable := NewMarkdownTable("", "") + + noHeaderTable.AddRow(MarkAsBold("Violation Severity:"), severity) + noHeaderTable.AddRow(MarkAsBold("Type:"), violationType) + noHeaderTable.AddRow(MarkAsBold("Policies:"), strings.Join(policies, ", ")) + noHeaderTable.AddRow(MarkAsBold("Watch name:"), watch) + + return noHeaderTable +} + +func getBaseDependencyViolationDetailsTable(direct []string, impacted, severity, violationType, watch string, policies []string, writer OutputWriter) *MarkdownTableBuilder { + noHeaderTable := getBaseViolationDetailsTable(severity, violationType, watch, policies, writer) + + noHeaderTable.AddRow(MarkAsBold("Direct Dependency:"), strings.Join(direct, ", ")) + noHeaderTable.AddRow(MarkAsBold("Impacted Dependency:"), impacted) + + return noHeaderTable +} + +// func getBaseJasViolationDetailsTable(ruleId, file, line, severity, violationType, watch string, policies []string, writer OutputWriter) *MarkdownTableBuilder { +// noHeaderTable := getBaseViolationDetailsTable(severity, violationType, watch, policies, writer) + +// noHeaderTable.AddRow(MarkAsBold("Rule ID:"), ruleId) +// noHeaderTable.AddRow(MarkAsBold("File Path:"), file) +// noHeaderTable.AddRow(MarkAsBold("Line:"), line) + +// return noHeaderTable +// } + +func VulnerabilitiesContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { + if len(vulnerabilities) == 0 { + return []string{} } + content = append(content, writer.MarkAsTitle(vulnerableDependenciesTitle, 2)) + content = append(content, vulnerabilitiesSummaryContent(vulnerabilities, writer)) + content = append(content, vulnerabilityDetailsContent(vulnerabilities, writer)...) + return +} +func vulnerabilitiesSummaryContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { + var contentBuilder strings.Builder + WriteContent(&contentBuilder, + writer.MarkAsTitle("✍️ Summary", 3), + writer.MarkInCenter(getVulnerabilitiesSummaryTable(vulnerabilities, writer)), + ) + return contentBuilder.String() } func getVulnerabilitiesSummaryTable(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { @@ -175,7 +522,7 @@ func getVulnerabilitiesSummaryTable(vulnerabilities []formats.VulnerabilityOrVio getDirectDependenciesCellData("%s:%s", vulnerability.Components), NewCellData(fmt.Sprintf("%s %s", vulnerability.ImpactedDependencyName, vulnerability.ImpactedDependencyVersion)), NewCellData(vulnerability.FixedVersions...), - getCveIdsCellData(vulnerability.Cves), + getCveIdsCellData(vulnerability.Cves, vulnerability.IssueId), ) table.AddRowWithCellData(row...) } @@ -192,9 +539,9 @@ func getDirectDependenciesCellData(format string, components []formats.Component return } -func getCveIdsCellData(cveRows []formats.CveRow) (ids CellData) { +func getCveIdsCellData(cveRows []formats.CveRow, issueId string) (ids CellData) { if len(cveRows) == 0 { - return NewCellData() + return NewCellData(issueId) } for _, cve := range cveRows { ids = append(ids, cve.Id) @@ -282,7 +629,7 @@ func getVulnerabilityDescriptionIdentifier(cveRows []formats.CveRow, xrayId stri return fmt.Sprintf("[ %s ]", identifier) } -func LicensesContent(licenses []formats.LicenseRow, writer OutputWriter) string { +func LicensesContent(licenses []formats.LicenseViolationRow, writer OutputWriter) string { if len(licenses) == 0 { return "" } @@ -351,6 +698,8 @@ func ApplicableCveReviewContent(severity, finding, fullDetails, cve, cveDetails, return contentBuilder.String() } +// JAS + func getJasDescriptionTable(severity, finding string, writer OutputWriter) string { return NewMarkdownTable("Severity", "Finding").AddRow(writer.FormattedSeverity(severity, "Applicable"), finding).Build() } @@ -358,7 +707,7 @@ func getJasDescriptionTable(severity, finding string, writer OutputWriter) strin func IacReviewContent(severity, finding, fullDetails string, writer OutputWriter) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, - writer.MarkAsTitle(iacTitle, 2), + writer.MarkAsTitle(fmt.Sprintf("%s Vulnerability", iacTitle), 2), writer.MarkInCenter(getJasDescriptionTable(severity, finding, writer)), writer.MarkAsDetails("Full description", 3, fullDetails), ) @@ -394,3 +743,78 @@ func sastDataFlowLocationsReviewContent(flow []formats.Location) string { } return contentBuilder.String() } + +// Jas Violation + +func getJasViolationFullDescription(issue formats.SourceCodeRow, tableDetailsContent string, writer OutputWriter) string { + var contentBuilder strings.Builder + // Write the violation details + WriteContent(&contentBuilder, writer.MarkAsDetails("Violation Details", 4, tableDetailsContent)) + // Separator + WriteNewLine(&contentBuilder) + // Write the description + WriteContent(&contentBuilder, issue.ScannerDescription) + return contentBuilder.String() +} + +func IacViolationReviewContent(issue formats.SourceCodeRow, writer OutputWriter) string { + var contentBuilder strings.Builder + WriteContent(&contentBuilder, + writer.MarkAsTitle(fmt.Sprintf("%s Violation", iacTitle), 2), + writer.MarkInCenter(getJasIssueDescriptionTable(issue, writer)), + writer.MarkAsDetails("Full description", 3, getIacViolationFullDescription(issue, writer)), + ) + return contentBuilder.String() +} + +func getJasIssueDescriptionTable(issue formats.SourceCodeRow, writer OutputWriter) string { + columns := []string{"Severity"} + rowData := []string{writer.FormattedSeverity(issue.Severity, "Applicable")} + // Optional ID column + if issue.IssueId != "" { + columns = append(columns, "ID") + rowData = append(rowData, issue.IssueId) + } + columns = append(columns, "Finding") + rowData = append(rowData, issue.Finding) + return NewMarkdownTable(columns...).AddRow(rowData...).Build() +} + +func getIacViolationFullDescription(issue formats.SourceCodeRow, writer OutputWriter) string { + return getJasViolationFullDescription(issue, getBaseJasViolationDetailsTable(issue.Watch, issue.Policies, []string{issue.CWE}, writer).Build(), writer) +} + +func getBaseJasViolationDetailsTable(watch string, policies, cwe []string, writer OutputWriter) *MarkdownTableBuilder { + noHeaderTable := NewMarkdownTable("", "").SetDelimiter(writer.Separator()) + if len(policies) > 0 { + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(policies...)) + } + if watch != "" { + noHeaderTable.AddRow(MarkAsBold("Watch Name:"), watch) + } + if len(cwe) > 0 { + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("CWE:")), NewCellData(cwe...)) + } + return noHeaderTable +} + +func SastViolationReviewContent(issue formats.SourceCodeRow, writer OutputWriter) string { + var contentBuilder strings.Builder + WriteContent(&contentBuilder, + writer.MarkAsTitle(fmt.Sprintf("%s Violation", sastTitle), 2), + writer.MarkInCenter(getJasIssueDescriptionTable(issue, writer)), + writer.MarkAsDetails("Full description", 3, getSastViolationFullDescription(issue, writer)), + ) + + if len(issue.CodeFlow) > 0 { + WriteContent(&contentBuilder, writer.MarkAsDetails("Code Flows", 3, sastCodeFlowsReviewContent(issue.CodeFlow, writer))) + } + + return contentBuilder.String() +} + +func getSastViolationFullDescription(issue formats.SourceCodeRow, writer OutputWriter) string { + table := getBaseJasViolationDetailsTable(issue.Watch, issue.Policies, []string{issue.CWE}, writer) + table.AddRow(MarkAsBold("Rule ID:"), issue.RuleId) + return getJasViolationFullDescription(issue, table.Build(), writer) +} diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index 38d0ff07d..d5587841a 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -411,12 +411,12 @@ func TestVulnerabilitiesContent(t *testing.T) { func TestLicensesContent(t *testing.T) { testCases := []struct { name string - licenses []formats.LicenseRow + licenses []formats.LicenseViolationRow cases []OutputTestCase }{ { name: "No license violations", - licenses: []formats.LicenseRow{}, + licenses: []formats.LicenseViolationRow{}, cases: []OutputTestCase{ { name: "Standard output", @@ -432,37 +432,40 @@ func TestLicensesContent(t *testing.T) { }, { name: "License violations", - licenses: []formats.LicenseRow{ - { - LicenseKey: "License1", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - - Components: []formats.ComponentRow{{Name: "Comp1", Version: "1.0"}}, - ImpactedDependencyName: "Dep1", - ImpactedDependencyVersion: "2.0", - SeverityDetails: formats.SeverityDetails{ - Severity: "High", + licenses: []formats.LicenseViolationRow{ + { + LicenseRow: formats.LicenseRow{ + LicenseKey: "License1", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + Components: []formats.ComponentRow{{Name: "Comp1", Version: "1.0"}}, + ImpactedDependencyName: "Dep1", + ImpactedDependencyVersion: "2.0", + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + }, }, }, }, { - LicenseKey: "License2", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - Components: []formats.ComponentRow{ - { - Name: "root", - Version: "1.0.0", + LicenseRow: formats.LicenseRow{ + LicenseKey: "License2", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + Components: []formats.ComponentRow{ + { + Name: "root", + Version: "1.0.0", + }, + { + Name: "minimatch", + Version: "1.2.3", + }, }, - { - Name: "minimatch", - Version: "1.2.3", + ImpactedDependencyName: "Dep2", + ImpactedDependencyVersion: "3.0", + SeverityDetails: formats.SeverityDetails{ + Severity: "High", }, }, - ImpactedDependencyName: "Dep2", - ImpactedDependencyVersion: "3.0", - SeverityDetails: formats.SeverityDetails{ - Severity: "High", - }, }, }, }, diff --git a/utils/outputwriter/outputwriter.go b/utils/outputwriter/outputwriter.go index 865c0ed02..0e3e8a48a 100644 --- a/utils/outputwriter/outputwriter.go +++ b/utils/outputwriter/outputwriter.go @@ -219,6 +219,10 @@ func MarkAsLink(content, link string) string { return fmt.Sprintf("[%s](%s)", content, link) } +func MarkAsNote(content string) string { + return fmt.Sprintf("> %s", content) +} + func SectionDivider() string { return "\n---" } diff --git a/utils/utils.go b/utils/utils.go index 1f16d0d01..54a006c0a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -13,6 +13,7 @@ import ( "strings" "sync" + "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-cli-core/v2/common/commands" @@ -336,7 +337,7 @@ func GetVulnerabiltiesUniqueID(vulnerability formats.VulnerabilityOrViolationRow len(vulnerability.FixedVersions) > 0) } -func ConvertSarifPathsToRelative(issues *IssuesCollection, workingDirs ...string) { +func ConvertSarifPathsToRelative(issues *issues.ScansIssuesCollection, workingDirs ...string) { convertSarifPathsInCveApplicability(issues.ScaVulnerabilities, workingDirs...) convertSarifPathsInIacs(issues.IacVulnerabilities, workingDirs...) convertSarifPathsInSecrets(issues.SecretsVulnerabilities, workingDirs...) From ac38bfcc7a35601f4054a5d54b80332872707675 Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 4 Dec 2024 18:19:19 +0200 Subject: [PATCH 11/34] implement violation content --- go.mod | 2 +- go.sum | 6 +- scanpullrequest/scanpullrequest.go | 4 +- scanpullrequest/scanpullrequest_test.go | 35 +-- scanrepository/scanrepository_test.go | 13 +- ...eview_content_no_remediation_simplified.md | 2 +- ..._review_content_no_remediation_standard.md | 2 +- .../applicable_review_content_simplified.md | 2 +- .../applicable_review_content_standard.md | 2 +- .../iac_violation_review_content_standard.md | 34 +++ .../sast_violation_review_content_standard.md | 62 +++++ .../secret_review_content_no_ca_simplified.md | 43 +++ .../secret_review_content_no_ca_standard.md | 46 +++ .../secret_review_content_simplified.md | 43 +++ .../secrets/secret_review_content_standard.md | 46 +++ ...ecret_violation_review_content_standard.md | 54 ++++ .../license/license_violation_standard.md | 10 - .../summary/summary_standard.md | 12 + .../license/license_violation_simplified.md | 0 .../license/license_violation_standard.md | 26 ++ .../security/security_violations_standard.md | 25 ++ .../violations/violations_standard.md | 50 ++++ utils/comment.go | 21 +- utils/comment_test.go | 86 +++++- utils/consts.go | 24 +- utils/issues/issuescollection.go | 262 +++++++++++------- utils/outputwriter/outputcontent.go | 108 ++++++-- utils/outputwriter/outputcontent_test.go | 55 ++++ utils/outputwriter/outputwriter.go | 4 +- utils/params.go | 6 + 30 files changed, 896 insertions(+), 189 deletions(-) create mode 100644 testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md create mode 100644 testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md create mode 100644 testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md create mode 100644 testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md create mode 100644 testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md create mode 100644 testdata/messages/reviewcomment/secrets/secret_review_content_standard.md create mode 100644 testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md delete mode 100644 testdata/messages/summarycomment/license/license_violation_standard.md create mode 100644 testdata/messages/summarycomment/summary/summary_standard.md rename testdata/messages/summarycomment/{ => violations}/license/license_violation_simplified.md (100%) create mode 100644 testdata/messages/summarycomment/violations/license/license_violation_standard.md create mode 100644 testdata/messages/summarycomment/violations/security/security_violations_standard.md create mode 100644 testdata/messages/summarycomment/violations/violations_standard.md diff --git a/go.mod b/go.mod index 1d8ef49c8..810858ea1 100644 --- a/go.mod +++ b/go.mod @@ -122,7 +122,7 @@ require ( // eranturgeman:jas-violations-support // replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241203111641-2e4a959b3031 +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241204143029-e901cd468c75 // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc diff --git a/go.sum b/go.sum index fbee07e9f..05e61e85a 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241203111641-2e4a959b3031 h1:M+eMYoyLanh9FV4ST2ljiuHbqafysEryHVUtg4PCfYE= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241203111641-2e4a959b3031/go.mod h1:c4GycFVqXqYOjI/1FdJ1lUf//kqQBGEzds1iSn73OaM= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241204143029-e901cd468c75 h1:iUJrYLb6FWxcvLsrN8o8QEpULwcytMCJnzt3suCvqZo= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241204143029-e901cd468c75/go.mod h1:c4GycFVqXqYOjI/1FdJ1lUf//kqQBGEzds1iSn73OaM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= @@ -129,8 +129,6 @@ github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5 github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= github.com/jfrog/build-info-go v1.10.6 h1:zH1ZhXlVfi5DlFyunygHjrdOcnv5qxfeLqmsfD4+lc4= github.com/jfrog/build-info-go v1.10.6/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= -github.com/jfrog/build-info-go v1.10.6 h1:zH1ZhXlVfi5DlFyunygHjrdOcnv5qxfeLqmsfD4+lc4= -github.com/jfrog/build-info-go v1.10.6/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= github.com/jfrog/froggit-go v1.16.2 h1:F//S83iXH14qsCwYzv0zB2JtjS2pJVEsUoEmYA+37dQ= github.com/jfrog/froggit-go v1.16.2/go.mod h1:5VpdQfAcbuyFl9x/x8HGm7kVk719kEtW/8YJFvKcHPA= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index e109e1a4e..abbda2e85 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -122,7 +122,7 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er func toFailTaskStatus(repo *utils.Repository, issues *issues.ScansIssuesCollection) bool { failFlagSet := repo.FailOnSecurityIssues != nil && *repo.FailOnSecurityIssues - return failFlagSet && (issues.PresentableIssuesExists() || issues.ViolationsExists()) + return failFlagSet && issues.IssuesExists(repo.PullRequestSecretComments) } // Downloads Pull Requests branches code and audits them @@ -158,7 +158,7 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) defer func() { if issuesCollection != nil { - xsc.SendScanEndedEvent(scanDetails.XrayVersion, scanDetails.XscVersion, scanDetails.ServerDetails, scanDetails.MultiScanId, scanDetails.StartTime, issuesCollection.CountIssuesCollectionFindings(), err) + xsc.SendScanEndedEvent(scanDetails.XrayVersion, scanDetails.XscVersion, scanDetails.ServerDetails, scanDetails.MultiScanId, scanDetails.StartTime, issuesCollection.GetTotalIssues(), err) } }() diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index eb32178a4..539bd80e6 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -28,6 +28,7 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/results/conversion" "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/jfrog/jfrog-cli-security/utils/techutils" + "github.com/jfrog/jfrog-cli-security/utils/validations" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" @@ -63,12 +64,12 @@ func scaToDummySimpleJsonResults(response services.ScanResponse, applicable bool convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true, AllowedLicenses: allowedLicenses}) jasResults := &results.JasScansResults{} if applicable { - jasResults.ApplicabilityScanResults = append(jasResults.ApplicabilityScanResults, sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2023-4321", ""))) + jasResults.ApplicabilityScanResults = append(jasResults.ApplicabilityScanResults, validations.NewMockJasRuns(sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2023-4321", "")))...) } cmdResults := &results.SecurityCommandResults{EntitledForJas: applicable, Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "dummy"}, JasResults: jasResults, - ScaResults: &results.ScaScanResults{XrayResults: []services.ScanResponse{response}}, + ScaResults: &results.ScaScanResults{XrayResults: validations.NewMockScaResults(response)}, }}} return convertor.ConvertToSimpleJson(cmdResults) } @@ -98,14 +99,14 @@ func createJasDiff(t *testing.T, scanType jasutils.JasScanType, source, target [ func jasToDummySimpleJsonResults(scanType jasutils.JasScanType, jasScanResults []*sarif.Result) (formats.SimpleJsonResults, error) { convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true}) - jasResults := &results.JasScansResults{JasVulnerabilities: &results.JasScanResults{}} + jasResults := &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}} switch scanType { case jasutils.Sast: - jasResults.JasVulnerabilities.SastScanResults = append(jasResults.JasVulnerabilities.SastScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) + jasResults.JasVulnerabilities.SastScanResults = append(jasResults.JasVulnerabilities.SastScanResults, validations.NewMockJasRuns(sarifutils.CreateRunWithDummyResults(jasScanResults...))...) case jasutils.Secrets: - jasResults.JasVulnerabilities.SecretsScanResults = append(jasResults.JasVulnerabilities.SecretsScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) + jasResults.JasVulnerabilities.SecretsScanResults = append(jasResults.JasVulnerabilities.SecretsScanResults, validations.NewMockJasRuns(sarifutils.CreateRunWithDummyResults(jasScanResults...))...) case jasutils.IaC: - jasResults.JasVulnerabilities.IacScanResults = append(jasResults.JasVulnerabilities.IacScanResults, sarifutils.CreateRunWithDummyResults(jasScanResults...)) + jasResults.JasVulnerabilities.IacScanResults = append(jasResults.JasVulnerabilities.IacScanResults, validations.NewMockJasRuns(sarifutils.CreateRunWithDummyResults(jasScanResults...))...) } cmdResults := &results.SecurityCommandResults{EntitledForJas: true, Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "dummy"}, @@ -447,43 +448,43 @@ func TestGetAllIssues(t *testing.T) { auditResults := &results.SecurityCommandResults{EntitledForJas: true, Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "dummy"}, ScaResults: &results.ScaScanResults{ - XrayResults: []services.ScanResponse{{ + XrayResults: validations.NewMockScaResults(services.ScanResponse{ Vulnerabilities: []services.Vulnerability{ {Cves: []services.Cve{{Id: "CVE-2022-2122"}}, Severity: "High", Components: map[string]services.Component{"Dep-1": {FixedVersions: []string{"1.2.3"}}}}, {Cves: []services.Cve{{Id: "CVE-2023-3122"}}, Severity: "Low", Components: map[string]services.Component{"Dep-2": {FixedVersions: []string{"1.2.2"}}}}, }, Licenses: []services.License{{Key: "Apache-2.0", Components: map[string]services.Component{"Dep-1": {FixedVersions: []string{"1.2.3"}}}}}, - }}, + }), }, JasResults: &results.JasScansResults{ - ApplicabilityScanResults: []*sarif.Run{ + ApplicabilityScanResults: validations.NewMockJasRuns( sarifutils.CreateRunWithDummyResults( sarifutils.CreateDummyPassingResult("applic_CVE-2023-3122"), sarifutils.CreateResultWithOneLocation("file1", 1, 10, 2, 11, "snippet", "applic_CVE-2022-2122", ""), ), - }, - JasVulnerabilities: &results.JasScanResults{ - IacScanResults: []*sarif.Run{ + ), + JasVulnerabilities: results.JasScanResults{ + IacScanResults: validations.NewMockJasRuns( sarifutils.CreateRunWithDummyResults( sarifutils.CreateResultWithLocations("Missing auto upgrade was detected", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), sarifutils.CreateLocation("file1", 1, 10, 2, 11, "aws-violation"), ), ), - }, - SecretsScanResults: []*sarif.Run{ + ), + SecretsScanResults: validations.NewMockJasRuns( sarifutils.CreateRunWithDummyResults( sarifutils.CreateResultWithLocations("Secret", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), sarifutils.CreateLocation("index.js", 5, 6, 7, 8, "access token exposed"), ), ), - }, - SastScanResults: []*sarif.Run{ + ), + SastScanResults: validations.NewMockJasRuns( sarifutils.CreateRunWithDummyResults( sarifutils.CreateResultWithLocations("XSS Vulnerability", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), sarifutils.CreateLocation("file1", 1, 10, 2, 11, "snippet"), ), ), - }, + ), }, }, }}} diff --git a/scanrepository/scanrepository_test.go b/scanrepository/scanrepository_test.go index c922e6e3b..3d6548361 100644 --- a/scanrepository/scanrepository_test.go +++ b/scanrepository/scanrepository_test.go @@ -21,6 +21,7 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/techutils" + "github.com/jfrog/jfrog-cli-security/utils/validations" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" @@ -489,8 +490,8 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { scanResults: &results.SecurityCommandResults{Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "target1"}, ScaResults: &results.ScaScanResults{ - XrayResults: []services.ScanResponse{ - { + XrayResults: validations.NewMockScaResults( + services.ScanResponse{ Vulnerabilities: []services.Vulnerability{ { Cves: []services.Cve{ @@ -520,7 +521,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { }, }, }, - }, + ), }, JasResults: &results.JasScansResults{}, }}}, @@ -541,8 +542,8 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { scanResults: &results.SecurityCommandResults{Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "target1"}, ScaResults: &results.ScaScanResults{ - XrayResults: []services.ScanResponse{ - { + XrayResults: validations.NewMockScaResults( + services.ScanResponse{ Violations: []services.Violation{ { ViolationType: "security", @@ -574,7 +575,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { }, }, }, - }, + ), }, JasResults: &results.JasScansResults{}, }}}, diff --git a/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_simplified.md b/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_simplified.md index a372250ce..d47789c99 100644 --- a/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_simplified.md +++ b/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_simplified.md @@ -1,7 +1,7 @@ --- -## 📦🔍 Contextual Analysis CVE Vulnerability +## 📦🔍 Contextual Analysis CVE --- | Severity | Impacted Dependency | Finding | CVE | diff --git a/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_standard.md b/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_standard.md index 610aeac07..d8ed4f938 100644 --- a/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_standard.md +++ b/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_standard.md @@ -1,5 +1,5 @@ -## 📦🔍 Contextual Analysis CVE Vulnerability +## 📦🔍 Contextual Analysis CVE
| Severity | Impacted Dependency | Finding | CVE | diff --git a/testdata/messages/reviewcomment/applicable/applicable_review_content_simplified.md b/testdata/messages/reviewcomment/applicable/applicable_review_content_simplified.md index 3e04f85ba..60a38a600 100644 --- a/testdata/messages/reviewcomment/applicable/applicable_review_content_simplified.md +++ b/testdata/messages/reviewcomment/applicable/applicable_review_content_simplified.md @@ -1,7 +1,7 @@ --- -## 📦🔍 Contextual Analysis CVE Vulnerability +## 📦🔍 Contextual Analysis CVE --- | Severity | Impacted Dependency | Finding | CVE | diff --git a/testdata/messages/reviewcomment/applicable/applicable_review_content_standard.md b/testdata/messages/reviewcomment/applicable/applicable_review_content_standard.md index 6cc4fe622..b02af8388 100644 --- a/testdata/messages/reviewcomment/applicable/applicable_review_content_standard.md +++ b/testdata/messages/reviewcomment/applicable/applicable_review_content_standard.md @@ -1,5 +1,5 @@ -## 📦🔍 Contextual Analysis CVE Vulnerability +## 📦🔍 Contextual Analysis CVE
| Severity | Impacted Dependency | Finding | CVE | diff --git a/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md b/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md new file mode 100644 index 000000000..8abc2bcbe --- /dev/null +++ b/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md @@ -0,0 +1,34 @@ + +## 🛠️ Infrastructure as Code Violation +
+ +| Severity | ID | Finding | +| :---------------------: | :-----------------------------------: |:-----------------------------------: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | VIOLATION_ID | Missing auto upgrade was detected | + +
+ +
+ Full description + +#### Violation Details +| | | +| :--- | :--- | +__Policies:__| xsc-policy-1 +__Watch Name:__| xsc-watch +__CWE:__| CWE-89 + + +Resource `google_container_node_pool` should have `management.auto_upgrade=true` + +Vulnerable example - +``` +resource "google_container_node_pool" "vulnerable_example" { + management { + auto_upgrade = false + } +} +``` + + +
diff --git a/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md b/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md new file mode 100644 index 000000000..e4365979b --- /dev/null +++ b/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md @@ -0,0 +1,62 @@ + +## 🎯 Static Application Security Testing (SAST) Violation +
+ +| Severity | ID | Finding | +| :---------------------: | :-----------------------------------: |:-----------------------------------: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | VIOLATION_ID | Stack Trace Exposure | + +
+ +
+Full description + +### Violation Details +| | | +| :--- | :--- | +__Policies:__| xsc-policy-1 +__Watch Name:__| xsc-watch +__CWE:__| CWE-89 +__Rule ID:__| java-sql-injection + + +### Overview +Stack trace exposure is a type of security vulnerability that occurs when a program reveals +sensitive information, such as the names and locations of internal files and variables, +in error messages or other diagnostic output. This can happen when a program crashes or +encounters an error, and the stack trace (a record of the program's call stack at the time +of the error) is included in the output. + +
+ +
+ Code Flows +
+ + +
+ Vulnerable data flow analysis result +
+ + +↘️ `other-snippet` (at file2 line 1) + +↘️ `snippet` (at file line 0) + + +
+ +
+ Vulnerable data flow analysis result +
+ + +↘️ `a-snippet` (at file line 10) + +↘️ `snippet` (at file line 0) + + +
+ + +
diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md new file mode 100644 index 000000000..26afae746 --- /dev/null +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md @@ -0,0 +1,43 @@ + + +--- +## 🗝️ Secret Detected + +--- +| Severity | Finding | +| :---------------------: | :-----------------------------------: | +| Medium | Secret keys were found | + +--- +### Full description + +--- +Storing hardcoded secrets in your source code or binary artifact could lead to several risks. + +If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. + +## Best practices + +Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - + +* ### Environment Variables + +Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - +`SECRET_VAR=MySecret ./my_application` +This way, `MySecret` does not have to be hardcoded into `my_application`. + +Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. + +* ### Secret management services + +External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - + +* [Hashicorp Vault](https://www.vaultproject.io) +* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) +* [Google Cloud KMS](https://cloud.google.com/security-key-management) + +## Least-privilege principle + +Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. +For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. +That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. \ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md new file mode 100644 index 000000000..5cd7222ef --- /dev/null +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md @@ -0,0 +1,46 @@ + +## 🗝️ Secret Detected +
+ +| Severity | Finding | +| :---------------------: | :-----------------------------------: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Secret keys were found | + +
+ +
+ Full description +
+ +Storing hardcoded secrets in your source code or binary artifact could lead to several risks. + +If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. + +## Best practices + +Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - + +* ### Environment Variables + +Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - +`SECRET_VAR=MySecret ./my_application` +This way, `MySecret` does not have to be hardcoded into `my_application`. + +Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. + +* ### Secret management services + +External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - + +* [Hashicorp Vault](https://www.vaultproject.io) +* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) +* [Google Cloud KMS](https://cloud.google.com/security-key-management) + +## Least-privilege principle + +Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. +For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. +That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. + + +
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md b/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md new file mode 100644 index 000000000..82a12945f --- /dev/null +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md @@ -0,0 +1,43 @@ + + +--- +## 🗝️ Secret Detected + +--- +| Severity | Finding | Status | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | +| Medium | Secret keys were found | Active | + +--- +### Full description + +--- +Storing hardcoded secrets in your source code or binary artifact could lead to several risks. + +If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. + +## Best practices + +Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - + +* ### Environment Variables + +Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - +`SECRET_VAR=MySecret ./my_application` +This way, `MySecret` does not have to be hardcoded into `my_application`. + +Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. + +* ### Secret management services + +External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - + +* [Hashicorp Vault](https://www.vaultproject.io) +* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) +* [Google Cloud KMS](https://cloud.google.com/security-key-management) + +## Least-privilege principle + +Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. +For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. +That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. \ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md b/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md new file mode 100644 index 000000000..ff4376e74 --- /dev/null +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md @@ -0,0 +1,46 @@ + +## 🗝️ Secret Detected +
+ +| Severity | Finding | Status | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Secret keys were found | Active | + +
+ +
+ Full description +
+ +Storing hardcoded secrets in your source code or binary artifact could lead to several risks. + +If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. + +## Best practices + +Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - + +* ### Environment Variables + +Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - +`SECRET_VAR=MySecret ./my_application` +This way, `MySecret` does not have to be hardcoded into `my_application`. + +Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. + +* ### Secret management services + +External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - + +* [Hashicorp Vault](https://www.vaultproject.io) +* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) +* [Google Cloud KMS](https://cloud.google.com/security-key-management) + +## Least-privilege principle + +Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. +For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. +That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. + + +
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md new file mode 100644 index 000000000..1046afafb --- /dev/null +++ b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md @@ -0,0 +1,54 @@ + +## 🗝️ Secret Violation +
+ +| Severity |ID | Finding | Status | +| :---------------------: | :-----------------------------------: |:-----------------------------------: | :-----------------------------------: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | VIOLATION_ID | Secret keys were found | Active | + +
+ +
+ Full description + +### Violation Details +| | | +| :--- | :--- | +__Policies:__| xsc-policy-1 +__Watch Name:__| xsc-watch +__CWE:__| CWE-89 +__Abbreviation:__| java-sql-injection + + +Storing hardcoded secrets in your source code or binary artifact could lead to several risks. + +If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. + +## Best practices + +Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - + +* ### Environment Variables + +Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - +`SECRET_VAR=MySecret ./my_application` +This way, `MySecret` does not have to be hardcoded into `my_application`. + +Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. + +* ### Secret management services + +External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - + +* [Hashicorp Vault](https://www.vaultproject.io) +* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) +* [Google Cloud KMS](https://cloud.google.com/security-key-management) + +## Least-privilege principle + +Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. +For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. +That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. + + +
\ No newline at end of file diff --git a/testdata/messages/summarycomment/license/license_violation_standard.md b/testdata/messages/summarycomment/license/license_violation_standard.md deleted file mode 100644 index b94665bfb..000000000 --- a/testdata/messages/summarycomment/license/license_violation_standard.md +++ /dev/null @@ -1,10 +0,0 @@ - -## ⚖️ Violated Licenses -
- -| SEVERITY | LICENSE | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | -| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| High | License1 | Comp1 1.0 | Dep1 2.0 | -| High | License2 | root 1.0.0
minimatch 1.2.3 | Dep2 3.0 | - -
diff --git a/testdata/messages/summarycomment/summary/summary_standard.md b/testdata/messages/summarycomment/summary/summary_standard.md new file mode 100644 index 000000000..e6b858601 --- /dev/null +++ b/testdata/messages/summarycomment/summary/summary_standard.md @@ -0,0 +1,12 @@ +## 📗 Scan Summary +- Frogbot scanned for violations and found 17 violations. + +| Scan Category | Status | Security Issues +| :--- | :--- | :--- | +__Software Composition Analysis__ | ✅ Done |
__17 Issues Found__❗️ 5 Critical
🔴 9 High
🟠 3 Medium +__Contextual Analysis__ | ✅ Done +__Static Application Security Testing (SAST)__ | ✅ Done | __Not Found__ +__Secrets__ | ❌ Failed +__Infrastucture as Code (IaC)__ | ℹ️ Not Scanned +
+
diff --git a/testdata/messages/summarycomment/license/license_violation_simplified.md b/testdata/messages/summarycomment/violations/license/license_violation_simplified.md similarity index 100% rename from testdata/messages/summarycomment/license/license_violation_simplified.md rename to testdata/messages/summarycomment/violations/license/license_violation_simplified.md diff --git a/testdata/messages/summarycomment/violations/license/license_violation_standard.md b/testdata/messages/summarycomment/violations/license/license_violation_standard.md new file mode 100644 index 000000000..66c1ac2dc --- /dev/null +++ b/testdata/messages/summarycomment/violations/license/license_violation_standard.md @@ -0,0 +1,26 @@ +### License Violations + +
+ +| Severity | ID | Direct Dependency | Impacted Dependency | Watch Name| +| :---: | :---: | :---: | :---: | :---: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | BSD-3-Clause | werkzeug:1.0.1 | pyyaml:1.1.1 | xsc-license-watch | + +
+ +#### 🔖 Details + +
+BSD-3-Clause + +| | | +| :--- | :--- | +__Violation Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/medium.svg) Medium +__Policies:__| xsc-policy-1 +__Watch name:__| xsc-watch +__Direct Dependency:__| flask:1.1.2 +__Impacted Dependency:__| werkzeug:1.0.1 +__Full Name:__| BSD 3-Clause "New" or "Revised" License + +
+
\ No newline at end of file diff --git a/testdata/messages/summarycomment/violations/security/security_violations_standard.md b/testdata/messages/summarycomment/violations/security/security_violations_standard.md new file mode 100644 index 000000000..a95c9c8a5 --- /dev/null +++ b/testdata/messages/summarycomment/violations/security/security_violations_standard.md @@ -0,0 +1,25 @@ +### Security Violations + +
+ +| Severity/Risk | ID | Contextual Analysis | Component | File Path | Watch Name| +| :---: | :---: | :---: | :---: | :---: | :---: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
Critical | CVE-2022-29361 | ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/applicable.svg) | werkzeug:1.0.1 | - | xsc-watch | +
+ +#### 🔖 Details + +
+CVE-2022-29361 + +| | | +| :--- | :--- | +__Violation Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/critical.svg) Critical +__Policies:__| xsc-policy-1 +__Watch name:__| xsc-watch +__JFrog Research Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/critical.svg) Critical +__Contextual Analysis:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/applicable.svg) +__Direct Dependency:__| flask:1.1.2 +__Impacted Dependency:__| werkzeug:1.0.1 +__Fix Versions:__| 5.3.1, 1.22.5 +__CVSS V3:__| 9.8 diff --git a/testdata/messages/summarycomment/violations/violations_standard.md b/testdata/messages/summarycomment/violations/violations_standard.md new file mode 100644 index 000000000..a9f78101c --- /dev/null +++ b/testdata/messages/summarycomment/violations/violations_standard.md @@ -0,0 +1,50 @@ + +## 📦 Vulnerable Dependencies + +### ✍️ Summary +
+ +| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | 4.0.0
5.0.0 | CVE-1111-11111 | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | - | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | CVE-2022-26652
CVE-2023-4321 | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | - | + +
+ + +### 🔬 Research Details + +
+ [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 +
+ + +**Description:** +Summary XRAY-122345 + +**Remediation:** +some remediation + +
+ +
+ [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 +
+ + +**Remediation:** +some remediation + +
+ +
+ github.com/mholt/archiver/v3 v3.5.1 +
+ + +**Description:** +Summary + +
diff --git a/utils/comment.go b/utils/comment.go index 40e1c0053..efd9d31ea 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -26,6 +26,7 @@ const ( ApplicableComment ReviewCommentType = "Applicable" IacComment ReviewCommentType = "Iac" SastComment ReviewCommentType = "Sast" + SecretComment ReviewCommentType = "Secrets" RescanRequestComment = "rescan" commentRemovalErrorMsg = "An error occurred while attempting to remove older Frogbot pull request comments:" @@ -186,7 +187,6 @@ func getFrogbotComments(writer outputwriter.OutputWriter, existingComments []vcs func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection) (commentsToAdd []ReviewComment) { writer := repo.OutputWriter - for _, vulnerability := range issues.GetScaIssues() { for _, cve := range vulnerability.Cves { if cve.Applicability != nil { @@ -202,6 +202,12 @@ func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection for _, sast := range issues.GetUniqueSastIssues() { commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeReviewContent(SastComment, sast, writer))) } + if !repo.Params.PullRequestSecretComments { + return + } + for _, secret := range issues.GetUniqueSecretsIssues() { + commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeReviewContent(SecretComment, secret, writer))) + } return } @@ -253,6 +259,19 @@ func generateSourceCodeReviewContent(commentType ReviewCommentType, issue format issue.CodeFlow, writer, ), writer) + case SecretComment: + applicability := "" + if issue.Applicability != nil { + applicability = issue.Applicability.Status + } + return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent( + issue.Severity, + issue.IssueId, + issue.Finding, + issue.ScannerDescription, + applicability, + writer, + ), writer) } return } diff --git a/utils/comment_test.go b/utils/comment_test.go index 2c1b709f4..baa554567 100644 --- a/utils/comment_test.go +++ b/utils/comment_test.go @@ -51,11 +51,14 @@ func TestGetFrogbotReviewComments(t *testing.T) { } func TestGetNewReviewComments(t *testing.T) { - repo := &Repository{OutputWriter: &outputwriter.StandardOutput{}} + writer := &outputwriter.StandardOutput{} + + // repo := &Repository{OutputWriter: &outputwriter.StandardOutput{}} testCases := []struct { - name string - issues *issues.ScansIssuesCollection - expectedOutput []ReviewComment + name string + generateSecretsComments bool + issues *issues.ScansIssuesCollection + expectedOutput []ReviewComment }{ { name: "No issues for review comments", @@ -93,6 +96,74 @@ func TestGetNewReviewComments(t *testing.T) { }, expectedOutput: []ReviewComment{}, }, + { + name: "Secret review comments", + generateSecretsComments: true, + issues: &issues.ScansIssuesCollection{ + ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ + { + Summary: "summary-2", + Applicable: "Applicable", + IssueId: "XRAY-2", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: formats.SeverityDetails{Severity: "low"}, + ImpactedDependencyName: "component-C", + }, + Cves: []formats.CveRow{{Id: "CVE-2023-4321"}}, + Technology: techutils.Npm, + }, + }, + SecretsVulnerabilities: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + IssueId: "id", + Finding: "secret finding", + Applicability: &formats.Applicability{Status: "Inactive"}, + Location: formats.Location{ + File: "index.js", + StartLine: 5, + StartColumn: 6, + EndLine: 7, + EndColumn: 8, + Snippet: "access token exposed", + }, + }, + }, + }, + expectedOutput: []ReviewComment{ + { + Location: formats.Location{ + File: "index.js", + StartLine: 5, + StartColumn: 6, + EndLine: 7, + EndColumn: 8, + Snippet: "access token exposed", + }, + Type: SecretComment, + CommentInfo: vcsclient.PullRequestComment{ + CommentInfo: vcsclient.CommentInfo{ + Content: outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent("High", "id", "secret finding", "", "Inactive", writer), writer), + }, + PullRequestDiff: vcsclient.PullRequestDiff{ + OriginalFilePath: "index.js", + OriginalStartLine: 5, + OriginalStartColumn: 6, + OriginalEndLine: 7, + OriginalEndColumn: 8, + NewFilePath: "index.js", + NewStartLine: 5, + NewStartColumn: 6, + NewEndLine: 7, + NewEndColumn: 8, + }, + }, + }, + }, + }, { name: "With issues for review comments", issues: &issues.ScansIssuesCollection{ @@ -157,7 +228,7 @@ func TestGetNewReviewComments(t *testing.T) { Type: ApplicableComment, CommentInfo: vcsclient.PullRequestComment{ CommentInfo: vcsclient.CommentInfo{ - Content: outputwriter.GenerateReviewCommentContent(outputwriter.ApplicableCveReviewContent("Low", "", "", "CVE-2023-4321", "summary-2", "component-C:", "", repo.OutputWriter), repo.OutputWriter), + Content: outputwriter.GenerateReviewCommentContent(outputwriter.ApplicableCveReviewContent("Low", "", "", "CVE-2023-4321", "summary-2", "component-C:", "", writer), writer), }, PullRequestDiff: vcsclient.PullRequestDiff{ OriginalFilePath: "file1", @@ -185,7 +256,7 @@ func TestGetNewReviewComments(t *testing.T) { Type: IacComment, CommentInfo: vcsclient.PullRequestComment{ CommentInfo: vcsclient.CommentInfo{ - Content: outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent("High", "Missing auto upgrade was detected", "", repo.OutputWriter), repo.OutputWriter), + Content: outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent("High", "Missing auto upgrade was detected", "", writer), writer), }, PullRequestDiff: vcsclient.PullRequestDiff{ OriginalFilePath: "file1", @@ -213,7 +284,7 @@ func TestGetNewReviewComments(t *testing.T) { Type: SastComment, CommentInfo: vcsclient.PullRequestComment{ CommentInfo: vcsclient.CommentInfo{ - Content: outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent("High", "XSS Vulnerability", "", [][]formats.Location{}, repo.OutputWriter), repo.OutputWriter), + Content: outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent("High", "XSS Vulnerability", "", [][]formats.Location{}, writer), writer), }, PullRequestDiff: vcsclient.PullRequestDiff{ OriginalFilePath: "file1", @@ -234,6 +305,7 @@ func TestGetNewReviewComments(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + repo := &Repository{OutputWriter: writer, Params: Params{Git: Git{PullRequestSecretComments: tc.generateSecretsComments}}} output := getNewReviewComments(repo, tc.issues) assert.ElementsMatch(t, tc.expectedOutput, output) }) diff --git a/utils/consts.go b/utils/consts.go index 3a8c46cf3..7f5e919fb 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -34,19 +34,21 @@ const ( JfrogConfigProfileEnv = "JF_CONFIG_PROFILE" // Git environment variables - GitProvider = "JF_GIT_PROVIDER" - GitRepoOwnerEnv = "JF_GIT_OWNER" - GitRepoEnv = "JF_GIT_REPO" - GitProjectEnv = "JF_GIT_PROJECT" - GitUsernameEnv = "JF_GIT_USERNAME" - GitUseLocalRepositoryEnv = "JF_USE_LOCAL_REPOSITORY" + GitProvider = "JF_GIT_PROVIDER" + GitRepoOwnerEnv = "JF_GIT_OWNER" + GitRepoEnv = "JF_GIT_REPO" + GitProjectEnv = "JF_GIT_PROJECT" + GitUsernameEnv = "JF_GIT_USERNAME" + GitUseLocalRepositoryEnv = "JF_USE_LOCAL_REPOSITORY" + UseMostCommonAncestorAsTargetEnv = "JF_USE_MOST_COMMON_ANCESTOR_AS_TARGET" // Git naming template environment variables - BranchNameTemplateEnv = "JF_BRANCH_NAME_TEMPLATE" - CommitMessageTemplateEnv = "JF_COMMIT_MESSAGE_TEMPLATE" - PullRequestTitleTemplateEnv = "JF_PULL_REQUEST_TITLE_TEMPLATE" - PullRequestCommentTitleEnv = "JF_PR_COMMENT_TITLE" - UseMostCommonAncestorAsTargetEnv = "JF_USE_MOST_COMMON_ANCESTOR_AS_TARGET" + BranchNameTemplateEnv = "JF_BRANCH_NAME_TEMPLATE" + CommitMessageTemplateEnv = "JF_COMMIT_MESSAGE_TEMPLATE" + PullRequestTitleTemplateEnv = "JF_PULL_REQUEST_TITLE_TEMPLATE" + PullRequestCommentTitleEnv = "JF_PR_COMMENT_TITLE" + //#nosec G101 -- not a secret + PullRequestSecretCommentsEnv = "JF_PR_SHOW_SECRETS_COMMENTS" // Repository environment variables - Ignored if the frogbot-config.yml file is used InstallCommandEnv = "JF_INSTALL_DEPS_CMD" diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index 97657e25e..42c78933c 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -1,7 +1,7 @@ package issues import ( - "github.com/jfrog/gofrog/datastructures" + // "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/severityutils" @@ -10,34 +10,102 @@ import ( // TODO: after refactor, move this to security-cli as a new formats or remove this and use the existing formats // Group issues by scan type type ScansIssuesCollection struct { + formats.ScanStatus + LicensesViolations []formats.LicenseViolationRow ScaVulnerabilities []formats.VulnerabilityOrViolationRow ScaViolations []formats.VulnerabilityOrViolationRow - ScaScanPerformed bool - ScaScanStatus int - - ApplicabilityScanPerformed bool - ApplicabilityScanStatus int IacVulnerabilities []formats.SourceCodeRow IacViolations []formats.SourceCodeRow - IacScan bool - IacScanStatus int SecretsVulnerabilities []formats.SourceCodeRow SecretsViolations []formats.SourceCodeRow - SecretsScanPerformed bool - SecretsScanStatus int SastViolations []formats.SourceCodeRow SastVulnerabilities []formats.SourceCodeRow - SastScanPerformed bool - SastScanStatus int } -func (ic *ScansIssuesCollection) GetTotalViolations() int { - return len(ic.ScaViolations) + len(ic.IacViolations) + len(ic.SecretsViolations) + len(ic.SastViolations) + len(ic.LicensesViolations) +// General methods + +func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { + if issues == nil { + return + } + // Status + ic.appendStatus(issues.ScanStatus) + // Sca + if len(issues.ScaVulnerabilities) > 0 { + ic.ScaVulnerabilities = append(ic.ScaVulnerabilities, issues.ScaVulnerabilities...) + } + if len(issues.ScaViolations) > 0 { + ic.ScaViolations = append(ic.ScaViolations, issues.ScaViolations...) + } + if len(issues.LicensesViolations) > 0 { + ic.LicensesViolations = append(ic.LicensesViolations, issues.LicensesViolations...) + } + // Secrets + if len(issues.SecretsVulnerabilities) > 0 { + ic.SecretsVulnerabilities = append(ic.SecretsVulnerabilities, issues.SecretsVulnerabilities...) + } + if len(issues.SecretsViolations) > 0 { + ic.SecretsViolations = append(ic.SecretsViolations, issues.SecretsViolations...) + } + // Sast + if len(issues.SastVulnerabilities) > 0 { + ic.SastVulnerabilities = append(ic.SastVulnerabilities, issues.SastVulnerabilities...) + } + if len(issues.SastViolations) > 0 { + ic.SastViolations = append(ic.SastViolations, issues.SastViolations...) + } + // Iac + if len(issues.IacVulnerabilities) > 0 { + ic.IacVulnerabilities = append(ic.IacVulnerabilities, issues.IacVulnerabilities...) + } + if len(issues.IacViolations) > 0 { + ic.IacViolations = append(ic.IacViolations, issues.IacViolations...) + } +} + +func (ic ScansIssuesCollection) appendStatus(scanStatus formats.ScanStatus) { + if ic.ScaStatusCode == nil || *ic.ScaStatusCode == 0 { + ic.ScaStatusCode = scanStatus.ScaStatusCode + } + if ic.IacStatusCode == nil || *ic.IacStatusCode == 0 { + ic.IacStatusCode = scanStatus.IacStatusCode + } + if ic.SecretsStatusCode == nil || *ic.SecretsStatusCode == 0 { + ic.SecretsStatusCode = scanStatus.SecretsStatusCode + } + if ic.SastStatusCode == nil || *ic.SastStatusCode == 0 { + ic.SastStatusCode = scanStatus.SastStatusCode + } + if ic.ApplicabilityStatusCode == nil || *ic.ApplicabilityStatusCode == 0 { + ic.ApplicabilityStatusCode = scanStatus.ApplicabilityStatusCode + } +} + +func (ic *ScansIssuesCollection) IsScanNotCompleted(scanType utils.SubScanType) bool { + status := ic.GetScanStatus(scanType) + // Failed or not performed scans + return status == nil || *status != 0 +} + +func (ic *ScansIssuesCollection) GetScanStatus(scanType utils.SubScanType) *int { + switch scanType { + case utils.ScaScan: + return ic.ScaStatusCode + case utils.IacScan: + return ic.IacStatusCode + case utils.SecretsScan: + return ic.SecretsStatusCode + case utils.SastScan: + return ic.SastStatusCode + case utils.ContextualAnalysisScan: + return ic.ApplicabilityStatusCode + } + return nil } func (ic *ScansIssuesCollection) GetScanDetails(scanType utils.SubScanType, violation bool) map[severityutils.Severity]int { @@ -85,117 +153,117 @@ func (ic *ScansIssuesCollection) GetScanDetails(scanType utils.SubScanType, viol return scanDetails } -func (ic *ScansIssuesCollection) GetScaIssues() (unique []formats.VulnerabilityOrViolationRow) { - return append(ic.ScaVulnerabilities, ic.ScaViolations...) +func (ic *ScansIssuesCollection) IssuesExists(includeSecrets bool) bool { + return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.SastIssuesExists() || (includeSecrets && ic.SecretsIssuesExists()) } func (ic *ScansIssuesCollection) ScaIssuesExists() bool { - return len(ic.ScaVulnerabilities) > 0 || len(ic.ScaViolations) > 0 -} - -func (ic *ScansIssuesCollection) GetUniqueIacIssues() (unique []formats.SourceCodeRow) { - return getUniqueJasIssues(ic.IacVulnerabilities, ic.IacViolations) + return len(ic.ScaVulnerabilities) > 0 || len(ic.ScaViolations) > 0 || len(ic.LicensesViolations) > 0 } func (ic *ScansIssuesCollection) IacIssuesExists() bool { return len(ic.IacVulnerabilities) > 0 || len(ic.IacViolations) > 0 } -func (ic *ScansIssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { - return getUniqueJasIssues(ic.SecretsVulnerabilities, ic.SecretsViolations) -} - func (ic *ScansIssuesCollection) SecretsIssuesExists() bool { return len(ic.SecretsVulnerabilities) > 0 || len(ic.SecretsViolations) > 0 } -func (ic *ScansIssuesCollection) GetUniqueSastIssues() (unique []formats.SourceCodeRow) { - return getUniqueJasIssues(ic.SastVulnerabilities, ic.SastViolations) -} - -func getUniqueJasIssues(vulnerabilities, violations []formats.SourceCodeRow) (unique []formats.SourceCodeRow) { - parsedIssues := datastructures.MakeSet[string]() - for _, violation := range violations { - issueId := violation.ToString() + "|" + violation.Finding - if parsedIssues.Exists(issueId) { - continue - } - parsedIssues.Add(issueId) - unique = append(unique, violation) - } - for _, vulnerability := range vulnerabilities { - issueId := vulnerability.ToString() + "|" + vulnerability.Finding - if parsedIssues.Exists(issueId) { - continue - } - parsedIssues.Add(issueId) - unique = append(unique, vulnerability) - } - return -} - func (ic *ScansIssuesCollection) SastIssuesExists() bool { return len(ic.SastVulnerabilities) > 0 || len(ic.SastViolations) > 0 } -func (ic *ScansIssuesCollection) LicensesViolationsExists() bool { - return len(ic.LicensesViolations) > 0 +func (ic *ScansIssuesCollection) GetTotalIssues() int { + return ic.GetTotalVulnerabilities() + ic.GetTotalViolations() } -func (ic *ScansIssuesCollection) PresentableIssuesExists() bool { - return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.LicensesViolationsExists() || ic.SastIssuesExists() +// Violations + +func (ic *ScansIssuesCollection) GetTotalViolations() int { + return len(ic.ScaViolations) + len(ic.IacViolations) + len(ic.SecretsViolations) + len(ic.SastViolations) + len(ic.LicensesViolations) } -func (ic *ScansIssuesCollection) ViolationsExists() bool { - return len(ic.ScaViolations) > 0 || len(ic.IacViolations) > 0 || len(ic.SecretsViolations) > 0 || len(ic.SastViolations) > 0 || len(ic.LicensesViolations) > 0 +// Vulnerabilities + +func (ic *ScansIssuesCollection) GetTotalVulnerabilities() int { + return len(ic.ScaVulnerabilities) + len(ic.IacVulnerabilities) + len(ic.SecretsVulnerabilities) + len(ic.SastVulnerabilities) } -func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { - if issues == nil { - return - } - if len(issues.ScaVulnerabilities) > 0 { - ic.ScaVulnerabilities = append(ic.ScaVulnerabilities, issues.ScaVulnerabilities...) - } - if len(issues.ScaViolations) > 0 { - ic.ScaViolations = append(ic.ScaViolations, issues.ScaViolations...) - } - if len(issues.SecretsVulnerabilities) > 0 { - ic.SecretsVulnerabilities = append(ic.SecretsVulnerabilities, issues.SecretsVulnerabilities...) - } - if len(issues.SecretsViolations) > 0 { - ic.SecretsViolations = append(ic.SecretsViolations, issues.SecretsViolations...) - } - if len(issues.SastVulnerabilities) > 0 { - ic.SastVulnerabilities = append(ic.SastVulnerabilities, issues.SastVulnerabilities...) - } - if len(issues.SastViolations) > 0 { - ic.SastViolations = append(ic.SastViolations, issues.SastViolations...) - } - if len(issues.IacVulnerabilities) > 0 { - ic.IacVulnerabilities = append(ic.IacVulnerabilities, issues.IacVulnerabilities...) - } - if len(issues.IacViolations) > 0 { - ic.IacViolations = append(ic.IacViolations, issues.IacViolations...) - } +// --------------------------------------- - if len(issues.LicensesViolations) > 0 { - ic.LicensesViolations = append(ic.LicensesViolations, issues.LicensesViolations...) - } -} -func (ic *ScansIssuesCollection) CountIssuesCollectionFindings() int { - count := 0 - count += len(ic.GetScaIssues()) - count += len(ic.GetUniqueIacIssues()) - count += len(ic.GetUniqueSecretsIssues()) - count += len(ic.GetUniqueSastIssues()) - count += len(ic.LicensesViolations) - return count -} + + + + + + + + +// func (ic *ScansIssuesCollection) GetScaIssues() (unique []formats.VulnerabilityOrViolationRow) { +// return append(ic.ScaVulnerabilities, ic.ScaViolations...) +// } + +// func (ic *ScansIssuesCollection) GetUniqueIacIssues() (unique []formats.SourceCodeRow) { +// return getUniqueJasIssues(ic.IacVulnerabilities, ic.IacViolations) +// } + + +// func (ic *ScansIssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { +// return getUniqueJasIssues(ic.SecretsVulnerabilities, ic.SecretsViolations) +// } + +// func (ic *ScansIssuesCollection) GetUniqueSastIssues() (unique []formats.SourceCodeRow) { +// return getUniqueJasIssues(ic.SastVulnerabilities, ic.SastViolations) +// } + +// func getUniqueJasIssues(vulnerabilities, violations []formats.SourceCodeRow) (unique []formats.SourceCodeRow) { +// parsedIssues := datastructures.MakeSet[string]() +// for _, violation := range violations { +// issueId := violation.Location.ToString() + "|" + violation.Finding +// if parsedIssues.Exists(issueId) { +// continue +// } +// parsedIssues.Add(issueId) +// unique = append(unique, violation) +// } +// for _, vulnerability := range vulnerabilities { +// issueId := vulnerability.Location.ToString() + "|" + vulnerability.Finding +// if parsedIssues.Exists(issueId) { +// continue +// } +// parsedIssues.Add(issueId) +// unique = append(unique, vulnerability) +// } +// return +// } + +// func (ic *ScansIssuesCollection) LicensesViolationsExists() bool { +// return len(ic.LicensesViolations) > 0 +// } + +// func (ic *ScansIssuesCollection) PresentableIssuesExists() bool { +// return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.LicensesViolationsExists() || ic.SastIssuesExists() +// } + +// func (ic *ScansIssuesCollection) ViolationsExists() bool { +// return len(ic.ScaViolations) > 0 || len(ic.IacViolations) > 0 || len(ic.SecretsViolations) > 0 || len(ic.SastViolations) > 0 || len(ic.LicensesViolations) > 0 +// } + +// func (ic *ScansIssuesCollection) CountIssuesCollectionFindings() int { +// count := 0 + +// count += len(ic.GetScaIssues()) +// count += len(ic.GetUniqueIacIssues()) +// count += len(ic.GetUniqueSecretsIssues()) +// count += len(ic.GetUniqueSastIssues()) +// count += len(ic.LicensesViolations) + +// return count +// } diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index bab20d4e4..64b37e4e0 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -8,6 +8,7 @@ import ( "github.com/jfrog/froggit-go/vcsutils" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" ) @@ -28,9 +29,11 @@ const ( vulnerableDependenciesTitle = "📦 Vulnerable Dependencies" vulnerableDependenciesResearchDetailsSubTitle = "🔬 Research Details" + //#nosec G101 -- not a secret + secretsTitle = "🗝️ Secret" contextualAnalysisTitle = "📦🔍 Contextual Analysis CVE" iacTitle = "🛠️ Infrastructure as Code" - sastTitle = "🎯 Static Application Security Testing (SAST) Vulnerability" + sastTitle = "🎯 Static Application Security Testing (SAST)" ) var ( @@ -130,27 +133,23 @@ func footer(writer OutputWriter) string { func scanSummaryContent(issues issues.ScansIssuesCollection, violations bool, writer OutputWriter) string { var contentBuilder strings.Builder issueType := "vulnerabilities" + totalIssues := issues.GetTotalVulnerabilities() if violations { issueType = "violations" + totalIssues = issues.GetTotalViolations() } // Title WriteContent(&contentBuilder, writer.MarkAsTitle(scanSummaryTitle, 2), - fmt.Sprintf("▶️ Frogbot scanned for %s and found %d issues", issueType, issues.GetTotalViolations()), + MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", issueType, totalIssues)), ) - // Summary - scaStatus, scaFailed := getSubScanResultStatus(issues.ScaScanPerformed, issues.ScaScanStatus) - applicabilityStatus, _ := getSubScanResultStatus(issues.ApplicabilityScanPerformed, issues.ApplicabilityScanStatus) - sastStatus, sastFailed := getSubScanResultStatus(issues.SastScanPerformed, issues.SastScanStatus) - secretsStatus, secretsFailed := getSubScanResultStatus(issues.SecretsScanPerformed, issues.SecretsScanStatus) - iacStatus, iacFailed := getSubScanResultStatus(issues.IacScan, issues.IacScanStatus) // Create table, a row for each sub scans summary - table := NewMarkdownTable("Scan Category", "Result", "Security Issues") - table.AddRow(MarkAsBold("Software Composition Analysis"), scaStatus, getScanSecurityIssuesDetails(issues, utils.ScaScan, scaFailed, violations, writer)) - table.AddRow(MarkAsBold("Contextual Analysis"), applicabilityStatus, "") - table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), sastStatus, getScanSecurityIssuesDetails(issues, utils.SastScan, sastFailed, violations, writer)) - table.AddRow(MarkAsBold("Secrets"), secretsStatus, getScanSecurityIssuesDetails(issues, utils.SecretsScan, secretsFailed, violations, writer)) - table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), iacStatus, getScanSecurityIssuesDetails(issues, utils.IacScan, iacFailed, violations, writer)) + table := NewMarkdownTable("Scan Category", "Status", "Security Issues") + table.AddRow(MarkAsBold("Software Composition Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.ScaScan, violations, writer)) + table.AddRow(MarkAsBold("Contextual Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ContextualAnalysisScan)), "") + table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.SastScan, violations, writer)) + table.AddRow(MarkAsBold("Secrets"), getSubScanResultStatus(issues.GetScanStatus(utils.SecretsScan)), getScanSecurityIssuesDetails(issues, utils.SecretsScan, violations, writer)) + table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), getSubScanResultStatus(issues.GetScanStatus(utils.IacScan)), getScanSecurityIssuesDetails(issues, utils.IacScan, violations, writer)) WriteContent(&contentBuilder, writer.MarkInCenter(table.Build())) // link to the scan results in JFrog // WriteNewLine(&contentBuilder) @@ -158,18 +157,18 @@ func scanSummaryContent(issues issues.ScansIssuesCollection, violations bool, wr return contentBuilder.String() } -func getSubScanResultStatus(scanPerformed bool, statusCode int) (string, bool) { - if !scanPerformed { - return "ℹ️ Not Scanned", false +func getSubScanResultStatus(scanStatusCode *int) string { + if scanStatusCode == nil { + return "ℹ️ Not Scanned" } - if statusCode == 0 { - return "✅ Done", false + if *scanStatusCode == 0 { + return "✅ Done" } - return "❌ Failed", true + return "❌ Failed" } -func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType utils.SubScanType, failed, violation bool, writer OutputWriter) string { - if failed || (scanType == utils.ScaScan && !issues.ScaScanPerformed) || (scanType == utils.SastScan && !issues.SastScanPerformed) || (scanType == utils.SecretsScan && !issues.SecretsScanPerformed) || (scanType == utils.IacScan && !issues.IacScan) { +func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType utils.SubScanType, violation bool, writer OutputWriter) string { + if issues.IsScanNotCompleted(scanType) { // Failed/Not scanned, no need to show the details return "" } @@ -189,7 +188,7 @@ func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType return "Not Found" } var contentBuilder strings.Builder - WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%d Issues Found", getTotalIssues(severityCountMap)), 3, toSeverityDetails(severityCountMap, writer))) + WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%d Issues Found", getTotalIssues(severityCountMap)), 3, toSeverityDetails(severityCountMap))) return contentBuilder.String() } @@ -200,7 +199,7 @@ func getTotalIssues(severities map[severityutils.Severity]int) (total int) { return } -func toSeverityDetails(severities map[severityutils.Severity]int, writer OutputWriter) string { +func toSeverityDetails(severities map[severityutils.Severity]int) string { var contentBuilder strings.Builder // Get severities with values and write them sorted (Critical, High, Medium, Low, Unknown) if count, ok := severities[severityutils.Critical]; ok && count > 0 { @@ -224,7 +223,7 @@ func toSeverityDetails(severities map[severityutils.Severity]int, writer OutputW // Policy Violations // Summary content for the security violations that we can't yet have location on (SCA, License) -func SecurityViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (content []string) { +func PolicyViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (content []string) { if issues.GetTotalViolations() == 0 { return []string{} } @@ -701,7 +700,17 @@ func ApplicableCveReviewContent(severity, finding, fullDetails, cve, cveDetails, // JAS func getJasDescriptionTable(severity, finding string, writer OutputWriter) string { - return NewMarkdownTable("Severity", "Finding").AddRow(writer.FormattedSeverity(severity, "Applicable"), finding).Build() + return NewMarkdownTable("Severity", "Finding").AddRow(writer.FormattedSeverity(severity, jasutils.Applicable.String()), finding).Build() +} + +func SecretReviewContent(severity, issueId, finding, fullDetails, applicability string, writer OutputWriter) string { + var contentBuilder strings.Builder + WriteContent(&contentBuilder, + writer.MarkAsTitle(fmt.Sprintf("%s Vulnerability", secretsTitle), 2), + writer.MarkInCenter(getSecretsDescriptionTable(severity, issueId, finding, applicability, writer)), + writer.MarkAsDetails("Full description", 3, fullDetails), + ) + return contentBuilder.String() } func IacReviewContent(severity, finding, fullDetails string, writer OutputWriter) string { @@ -717,7 +726,7 @@ func IacReviewContent(severity, finding, fullDetails string, writer OutputWriter func SastReviewContent(severity, finding, fullDetails string, codeFlows [][]formats.Location, writer OutputWriter) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, - writer.MarkAsTitle(sastTitle, 2), + writer.MarkAsTitle(fmt.Sprintf("%s Vulnerability", sastTitle), 2), writer.MarkInCenter(getJasDescriptionTable(severity, finding, writer)), writer.MarkAsDetails("Full description", 3, fullDetails), ) @@ -780,6 +789,31 @@ func getJasIssueDescriptionTable(issue formats.SourceCodeRow, writer OutputWrite return NewMarkdownTable(columns...).AddRow(rowData...).Build() } +func getSecretsDescriptionTable(severity, issueId, finding, status string, writer OutputWriter) string { + // Determine the issue applicable status + applicability := jasutils.Applicable.String() + if status != "" { + if status == jasutils.Inactive.String() { + applicability = jasutils.NotApplicable.String() + } + } + columns := []string{"Severity"} + rowData := []string{writer.FormattedSeverity(severity, applicability)} + // Determine if issueId is provided + if issueId != "" { + columns = append(columns, "ID") + rowData = append(rowData, issueId) + } + columns = append(columns, "Finding") + rowData = append(rowData, finding) + // Determine if status is provided + if status != "" { + columns = append(columns, "Status") + rowData = append(rowData, status) + } + return NewMarkdownTable(columns...).AddRow(rowData...).Build() +} + func getIacViolationFullDescription(issue formats.SourceCodeRow, writer OutputWriter) string { return getJasViolationFullDescription(issue, getBaseJasViolationDetailsTable(issue.Watch, issue.Policies, []string{issue.CWE}, writer).Build(), writer) } @@ -818,3 +852,23 @@ func getSastViolationFullDescription(issue formats.SourceCodeRow, writer OutputW table.AddRow(MarkAsBold("Rule ID:"), issue.RuleId) return getJasViolationFullDescription(issue, table.Build(), writer) } + +func SecretViolationReviewContent(issue formats.SourceCodeRow, writer OutputWriter) string { + applicability := "" + if issue.Applicability != nil { + applicability = issue.Applicability.Status + } + var contentBuilder strings.Builder + WriteContent(&contentBuilder, + writer.MarkAsTitle(fmt.Sprintf("%s Violation", secretsTitle), 2), + writer.MarkInCenter(getSecretsDescriptionTable(issue.Severity, issue.IssueId, issue.Finding, applicability, writer)), + writer.MarkAsDetails("Full description", 3, getSecretsViolationFullDescription(issue, writer)), + ) + return contentBuilder.String() +} + +func getSecretsViolationFullDescription(issue formats.SourceCodeRow, writer OutputWriter) string { + table := getBaseJasViolationDetailsTable(issue.Watch, issue.Policies, []string{issue.CWE}, writer) + table.AddRow(MarkAsBold("Abbreviation:"), issue.RuleId) + return getJasViolationFullDescription(issue, table.Build(), writer) +} diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index d5587841a..b44fa2304 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -639,6 +639,61 @@ func TestApplicableReviewContent(t *testing.T) { } } +func TestSecretsReviewContent(t *testing.T) { + testCases := []struct { + name string + severity, finding, fullDetails, status string + cases []OutputTestCase + }{ + { + name: "Secret review comment content", + severity: "Medium", + finding: "Secret keys were found", + fullDetails: "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_review_content_no_ca_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_review_content_no_ca_simplified.md")}, + }, + }, + }, + { + name: "Secret review comment content with applicability status", + severity: "Medium", + status: "Active", + finding: "Secret keys were found", + fullDetails: "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_review_content_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_review_content_simplified.md")}, + }, + }, + }, + } + + for _, tc := range testCases { + for _, test := range tc.cases { + t.Run(tc.name+"_"+test.name, func(t *testing.T) { + expectedOutput := GetExpectedTestOutput(t, test) + assert.Equal(t, expectedOutput, SecretReviewContent(tc.severity, "id", tc.finding, tc.fullDetails, tc.status, test.writer)) + }) + } + } +} + func TestIacReviewContent(t *testing.T) { testCases := []struct { name string diff --git a/utils/outputwriter/outputwriter.go b/utils/outputwriter/outputwriter.go index 0e3e8a48a..b50e677b4 100644 --- a/utils/outputwriter/outputwriter.go +++ b/utils/outputwriter/outputwriter.go @@ -219,8 +219,8 @@ func MarkAsLink(content, link string) string { return fmt.Sprintf("[%s](%s)", content, link) } -func MarkAsNote(content string) string { - return fmt.Sprintf("> %s", content) +func MarkAsBullet(content string) string { + return fmt.Sprintf("- %s", content) } func SectionDivider() string { diff --git a/utils/params.go b/utils/params.go index d3f15b2a4..de1ed7bcd 100644 --- a/utils/params.go +++ b/utils/params.go @@ -310,6 +310,7 @@ type Git struct { CommitMessageTemplate string `yaml:"commitMessageTemplate,omitempty"` PullRequestTitleTemplate string `yaml:"pullRequestTitleTemplate,omitempty"` PullRequestCommentTitle string `yaml:"pullRequestCommentTitle,omitempty"` + PullRequestSecretComments bool `yaml:"pullRequestSecretComments,omitempty"` AvoidExtraMessages bool `yaml:"avoidExtraMessages,omitempty"` EmailAuthor string `yaml:"emailAuthor,omitempty"` AggregateFixes bool `yaml:"aggregateFixes,omitempty"` @@ -355,6 +356,11 @@ func (g *Git) extractScanPullRequestEnvParams(gitParamsFromEnv *Git) (err error) if g.PullRequestCommentTitle == "" { g.PullRequestCommentTitle = getTrimmedEnv(PullRequestCommentTitleEnv) } + if !g.PullRequestSecretComments { + if g.PullRequestSecretComments, err = getBoolEnv(PullRequestSecretCommentsEnv, false); err != nil { + return + } + } if !g.UseMostCommonAncestorAsTarget { if g.UseMostCommonAncestorAsTarget, err = getBoolEnv(UseMostCommonAncestorAsTargetEnv, true); err != nil { return From fa6b8fca132ad7cb13e3909950049c8bfc859249 Mon Sep 17 00:00:00 2001 From: attiasas Date: Thu, 5 Dec 2024 11:46:05 +0200 Subject: [PATCH 12/34] almost done implement violation and summary content --- resources/v2/smallCritical.svg | 3 + resources/v2/smallHigh.svg | 3 + resources/v2/smallLow.svg | 3 + resources/v2/smallMedium.svg | 3 + resources/v2/smallUnknown.svg | 3 + scanpullrequest/scanpullrequest.go | 2 +- ...iac_violation_review_content_simplified.md | 24 + ...ast_violation_review_content_simplified.md | 46 + ...ret_violation_review_content_simplified.md | 43 + .../summary/summary_simplified.md | 12 + .../license/license_violation_standard.md | 20 +- .../security/security_violation_standard.md | 79 ++ .../security/security_violations_standard.md | 25 - .../violations/violations_standard.md | 50 - utils/comment.go | 90 +- utils/consts.go | 1 + utils/issues/issuescollection.go | 29 +- utils/outputwriter/icons.go | 24 + utils/outputwriter/outputcontent.go | 854 +++++++++--------- utils/outputwriter/outputcontent_test.go | 851 ++++++++++------- utils/outputwriter/outputwriter.go | 2 +- utils/outputwriter/simplifiedoutput.go | 2 +- utils/outputwriter/simplifiedoutput_test.go | 2 +- utils/outputwriter/standardoutput.go | 8 +- utils/outputwriter/standardoutput_test.go | 2 +- utils/params.go | 17 +- utils/utils.go | 9 + 27 files changed, 1265 insertions(+), 942 deletions(-) create mode 100644 resources/v2/smallCritical.svg create mode 100644 resources/v2/smallHigh.svg create mode 100644 resources/v2/smallLow.svg create mode 100644 resources/v2/smallMedium.svg create mode 100644 resources/v2/smallUnknown.svg create mode 100644 testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md create mode 100644 testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md create mode 100644 testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md create mode 100644 testdata/messages/summarycomment/summary/summary_simplified.md create mode 100644 testdata/messages/summarycomment/violations/security/security_violation_standard.md delete mode 100644 testdata/messages/summarycomment/violations/security/security_violations_standard.md delete mode 100644 testdata/messages/summarycomment/violations/violations_standard.md diff --git a/resources/v2/smallCritical.svg b/resources/v2/smallCritical.svg new file mode 100644 index 000000000..85d984521 --- /dev/null +++ b/resources/v2/smallCritical.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/v2/smallHigh.svg b/resources/v2/smallHigh.svg new file mode 100644 index 000000000..e367e459f --- /dev/null +++ b/resources/v2/smallHigh.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/v2/smallLow.svg b/resources/v2/smallLow.svg new file mode 100644 index 000000000..265842939 --- /dev/null +++ b/resources/v2/smallLow.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/v2/smallMedium.svg b/resources/v2/smallMedium.svg new file mode 100644 index 000000000..1018c9e4c --- /dev/null +++ b/resources/v2/smallMedium.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/v2/smallUnknown.svg b/resources/v2/smallUnknown.svg new file mode 100644 index 000000000..7d66301be --- /dev/null +++ b/resources/v2/smallUnknown.svg @@ -0,0 +1,3 @@ + + + diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index abbda2e85..692af4770 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -158,7 +158,7 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) defer func() { if issuesCollection != nil { - xsc.SendScanEndedEvent(scanDetails.XrayVersion, scanDetails.XscVersion, scanDetails.ServerDetails, scanDetails.MultiScanId, scanDetails.StartTime, issuesCollection.GetTotalIssues(), err) + xsc.SendScanEndedEvent(scanDetails.XrayVersion, scanDetails.XscVersion, scanDetails.ServerDetails, scanDetails.MultiScanId, scanDetails.StartTime, issuesCollection.GetTotalIssues(true), err) } }() diff --git a/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md b/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md new file mode 100644 index 000000000..c526eb6f4 --- /dev/null +++ b/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md @@ -0,0 +1,24 @@ + + +--- +## 🛠️ Infrastructure as Code Vulnerability + +--- +| Severity | Finding | +| :---------------------: | :-----------------------------------: | +| Medium | Missing auto upgrade was detected | + +--- +### Full description + +--- +Resource `google_container_node_pool` should have `management.auto_upgrade=true` + +Vulnerable example - +``` +resource "google_container_node_pool" "vulnerable_example" { + management { + auto_upgrade = false + } +} +``` diff --git a/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md b/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md new file mode 100644 index 000000000..366c7c4c2 --- /dev/null +++ b/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md @@ -0,0 +1,46 @@ + + +--- +## 🎯 Static Application Security Testing (SAST) Vulnerability + +--- +| Severity | Finding | +| :---------------------: | :-----------------------------------: | +| Low | Stack Trace Exposure | + +--- +### Full description + +--- + +### Overview +Stack trace exposure is a type of security vulnerability that occurs when a program reveals +sensitive information, such as the names and locations of internal files and variables, +in error messages or other diagnostic output. This can happen when a program crashes or +encounters an error, and the stack trace (a record of the program's call stack at the time +of the error) is included in the output. + +--- +### Code Flows + +--- + + +--- +#### Vulnerable data flow analysis result + +--- + +↘️ `other-snippet` (at file2 line 1) + +↘️ `snippet` (at file line 0) + + +--- +#### Vulnerable data flow analysis result + +--- + +↘️ `a-snippet` (at file line 10) + +↘️ `snippet` (at file line 0) diff --git a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md new file mode 100644 index 000000000..82a12945f --- /dev/null +++ b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md @@ -0,0 +1,43 @@ + + +--- +## 🗝️ Secret Detected + +--- +| Severity | Finding | Status | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | +| Medium | Secret keys were found | Active | + +--- +### Full description + +--- +Storing hardcoded secrets in your source code or binary artifact could lead to several risks. + +If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. + +## Best practices + +Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - + +* ### Environment Variables + +Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - +`SECRET_VAR=MySecret ./my_application` +This way, `MySecret` does not have to be hardcoded into `my_application`. + +Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. + +* ### Secret management services + +External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - + +* [Hashicorp Vault](https://www.vaultproject.io) +* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) +* [Google Cloud KMS](https://cloud.google.com/security-key-management) + +## Least-privilege principle + +Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. +For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. +That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_simplified.md b/testdata/messages/summarycomment/summary/summary_simplified.md new file mode 100644 index 000000000..e6b858601 --- /dev/null +++ b/testdata/messages/summarycomment/summary/summary_simplified.md @@ -0,0 +1,12 @@ +## 📗 Scan Summary +- Frogbot scanned for violations and found 17 violations. + +| Scan Category | Status | Security Issues +| :--- | :--- | :--- | +__Software Composition Analysis__ | ✅ Done |
__17 Issues Found__❗️ 5 Critical
🔴 9 High
🟠 3 Medium +__Contextual Analysis__ | ✅ Done +__Static Application Security Testing (SAST)__ | ✅ Done | __Not Found__ +__Secrets__ | ❌ Failed +__Infrastucture as Code (IaC)__ | ℹ️ Not Scanned +
+
diff --git a/testdata/messages/summarycomment/violations/license/license_violation_standard.md b/testdata/messages/summarycomment/violations/license/license_violation_standard.md index 66c1ac2dc..5eef28a61 100644 --- a/testdata/messages/summarycomment/violations/license/license_violation_standard.md +++ b/testdata/messages/summarycomment/violations/license/license_violation_standard.md @@ -1,26 +1,24 @@ -### License Violations +## 🚥 Policy Violations + +### ⚖️ License Violations
-| Severity | ID | Direct Dependency | Impacted Dependency | Watch Name| -| :---: | :---: | :---: | :---: | :---: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | BSD-3-Clause | werkzeug:1.0.1 | pyyaml:1.1.1 | xsc-license-watch | +| Severity | License | Direct Dependency | Impacted Dependency | Watch Name | +| :---: | :---: | :---: | :---: | :---: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | BSD-3-Clause | werkzeug:1.0.1 | pyyaml:1.1.1 | xsc-watch |
#### 🔖 Details
-BSD-3-Clause +[ BSD-3-Clause ] pyyaml 1.1.1 (xsc-watch) | | | | :--- | :--- | -__Violation Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/medium.svg) Medium -__Policies:__| xsc-policy-1 -__Watch name:__| xsc-watch -__Direct Dependency:__| flask:1.1.2 -__Impacted Dependency:__| werkzeug:1.0.1 -__Full Name:__| BSD 3-Clause "New" or "Revised" License +**Policies:** | xsc-policy-1 +**Full Name:** | BSD 3-Clause "New" or "Revised" License

\ No newline at end of file diff --git a/testdata/messages/summarycomment/violations/security/security_violation_standard.md b/testdata/messages/summarycomment/violations/security/security_violation_standard.md new file mode 100644 index 000000000..d12fe4839 --- /dev/null +++ b/testdata/messages/summarycomment/violations/security/security_violation_standard.md @@ -0,0 +1,79 @@ + +## 🚥 Policy Violations + +### 🚨 Security Violations + +
+ +| Severity/Risk | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Watch Name | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | sca-watch | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | CVE-2022-26652
CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | + +
+ + +### 🔖 Details + +
+ [ CVE-1111-11111 ] impacted 3.0.0 (sca-watch) + +### Violation Details + +| | | +| :--- | :--- | +__Policies:__| xsc-policy-1 +__Watch Name:__| xsc-watch +__JFrog Research Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/critical.svg) Critical +__Contextual Analysis:__| Applicable +__Direct Dependencies:__| flask:1.1.2 +__Impacted Dependency:__| werkzeug:1.0.1 +__Fix Versions:__| 4.0.0, 5.0.0 +__CVSS V3:__| 9.8 + +some-summary + +### 🔬 JFrog Research Details + +**Description:** +Summary XRAY-122345 + +**Remediation:** +some remediation + +
+ +
+ [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 +
+ + +**Description:** +Summary XRAY-122345 + +**Remediation:** +some remediation + +
+ +
+ [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 +
+ + +**Remediation:** +some remediation + +
+ +
+ github.com/mholt/archiver/v3 v3.5.1 +
+ + +**Description:** +Summary + +
diff --git a/testdata/messages/summarycomment/violations/security/security_violations_standard.md b/testdata/messages/summarycomment/violations/security/security_violations_standard.md deleted file mode 100644 index a95c9c8a5..000000000 --- a/testdata/messages/summarycomment/violations/security/security_violations_standard.md +++ /dev/null @@ -1,25 +0,0 @@ -### Security Violations - -
- -| Severity/Risk | ID | Contextual Analysis | Component | File Path | Watch Name| -| :---: | :---: | :---: | :---: | :---: | :---: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
Critical | CVE-2022-29361 | ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/applicable.svg) | werkzeug:1.0.1 | - | xsc-watch | -
- -#### 🔖 Details - -
-CVE-2022-29361 - -| | | -| :--- | :--- | -__Violation Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/critical.svg) Critical -__Policies:__| xsc-policy-1 -__Watch name:__| xsc-watch -__JFrog Research Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/critical.svg) Critical -__Contextual Analysis:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/applicable.svg) -__Direct Dependency:__| flask:1.1.2 -__Impacted Dependency:__| werkzeug:1.0.1 -__Fix Versions:__| 5.3.1, 1.22.5 -__CVSS V3:__| 9.8 diff --git a/testdata/messages/summarycomment/violations/violations_standard.md b/testdata/messages/summarycomment/violations/violations_standard.md deleted file mode 100644 index a9f78101c..000000000 --- a/testdata/messages/summarycomment/violations/violations_standard.md +++ /dev/null @@ -1,50 +0,0 @@ - -## 📦 Vulnerable Dependencies - -### ✍️ Summary -
- -| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | -| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | 4.0.0
5.0.0 | CVE-1111-11111 | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | - | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | CVE-2022-26652
CVE-2023-4321 | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | - | - -
- - -### 🔬 Research Details - -
- [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 -
- - -**Description:** -Summary XRAY-122345 - -**Remediation:** -some remediation - -
- -
- [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 -
- - -**Remediation:** -some remediation - -
- -
- github.com/mholt/archiver/v3 v3.5.1 -
- - -**Description:** -Summary - -
diff --git a/utils/comment.go b/utils/comment.go index efd9d31ea..3ea05646c 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -46,7 +46,7 @@ func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, re } // Add summary (SCA, license) scan comment - for _, comment := range generatePullRequestSummaryComment(issues, repo.OutputWriter) { + for _, comment := range generatePullRequestSummaryComment(issues, repo.ViolationContext, repo.PullRequestSecretComments, repo.OutputWriter) { if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, comment, pullRequestID); err != nil { err = errors.New("couldn't add pull request comment: " + err.Error()) return @@ -90,7 +90,7 @@ func DeleteExistingPullRequestComments(repository *Repository, client vcsclient. } func GenerateFixPullRequestDetails(vulnerabilities []formats.VulnerabilityOrViolationRow, writer outputwriter.OutputWriter) (description string, extraComments []string) { - content := outputwriter.GetPRSummaryContent(outputwriter.VulnerabilitiesContent(vulnerabilities, writer), true, false, writer) + content := outputwriter.GetMainCommentContent(outputwriter.GetVulnerabilitiesContent(vulnerabilities, writer), true, false, writer) if len(content) == 1 { // Limit is not reached, use the entire content as the description description = content[0] @@ -107,19 +107,22 @@ func GenerateFixPullRequestDetails(vulnerabilities []formats.VulnerabilityOrViol return } -func generatePullRequestSummaryComment(issuesCollection *issues.ScansIssuesCollection, writer outputwriter.OutputWriter) []string { - if !issuesCollection.PresentableIssuesExists() && !issuesCollection.ViolationsExists() { - return outputwriter.GetPRSummaryContent([]string{}, false, true, writer) +func generatePullRequestSummaryComment(issuesCollection *issues.ScansIssuesCollection, violationContext ViolationContext, includeSecrets bool, writer outputwriter.OutputWriter) []string { + if !issuesCollection.IssuesExists(includeSecrets) { + // No Issues + return outputwriter.GetMainCommentContent([]string{}, false, true, writer) } - content := []string{} - if vulnerabilitiesContent := outputwriter.VulnerabilitiesContent(issuesCollection.GetScaIssues(), writer); len(vulnerabilitiesContent) > 0 { - content = append(content, vulnerabilitiesContent...) + // if violationContext != None { + content = append(content, outputwriter.ScanSummaryContent(*issuesCollection, string(violationContext), includeSecrets, writer)) + // } + if violationsContent := outputwriter.PolicyViolationsContent(*issuesCollection, writer); len(violationsContent) > 0 { + content = append(content, violationsContent...) } - if licensesContent := outputwriter.LicensesContent(issuesCollection.LicensesViolations, writer); len(licensesContent) > 0 { - content = append(content, licensesContent) + if vulnerabilitiesContent := outputwriter.GetVulnerabilitiesContent(issuesCollection.ScaVulnerabilities, writer); len(vulnerabilitiesContent) > 0 { + content = append(content, vulnerabilitiesContent...) } - return outputwriter.GetPRSummaryContent(content, true, true, writer) + return outputwriter.GetMainCommentContent(content, true, true, writer) } func IsFrogbotRescanComment(comment string) bool { @@ -177,7 +180,7 @@ func DeleteExistingPullRequestReviewComments(repo *Repository, pullRequestID int func getFrogbotComments(writer outputwriter.OutputWriter, existingComments []vcsclient.CommentInfo) (reviewComments []vcsclient.CommentInfo) { for _, comment := range existingComments { - if outputwriter.IsFrogbotComment(comment.Content) || outputwriter.IsFrogbotSummaryComment(writer, comment.Content) { + if outputwriter.IsFrogbotComment(comment.Content) { log.Debug("Deleting comment id:", comment.ID) reviewComments = append(reviewComments, comment) } @@ -187,26 +190,23 @@ func getFrogbotComments(writer outputwriter.OutputWriter, existingComments []vcs func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection) (commentsToAdd []ReviewComment) { writer := repo.OutputWriter - for _, vulnerability := range issues.GetScaIssues() { - for _, cve := range vulnerability.Cves { - if cve.Applicability != nil { - for _, evidence := range cve.Applicability.Evidence { - commentsToAdd = append(commentsToAdd, generateReviewComment(ApplicableComment, evidence.Location, generateApplicabilityReviewContent(evidence, cve, vulnerability, writer))) - } - } - } + for _, applicableEvidence := range issues.GetApplicableEvidences() { + commentsToAdd = append(commentsToAdd, generateReviewComment(ApplicableComment, applicableEvidence.Location, generateApplicabilityReviewContent(applicableEvidence, cve, vulnerability, writer))) + } + for _, iac := range issues.IacVulnerabilities { + commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, iac.Location, generateSourceCodeVulnerabilityReviewContent(IacComment, iac, writer))) } - for _, iac := range issues.GetUniqueIacIssues() { - commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, iac.Location, generateSourceCodeReviewContent(IacComment, iac, writer))) + for _, iac := range issues.IacViolations { + } for _, sast := range issues.GetUniqueSastIssues() { - commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeReviewContent(SastComment, sast, writer))) + commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeVulnerabilityReviewContent(SastComment, sast, writer))) } if !repo.Params.PullRequestSecretComments { return } for _, secret := range issues.GetUniqueSecretsIssues() { - commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeReviewContent(SecretComment, secret, writer))) + commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeVulnerabilityReviewContent(SecretComment, secret, writer))) } return } @@ -242,36 +242,26 @@ func generateApplicabilityReviewContent(issue formats.Evidence, relatedCve forma ), writer) } -func generateSourceCodeReviewContent(commentType ReviewCommentType, issue formats.SourceCodeRow, writer outputwriter.OutputWriter) (content string) { +func generateSourceCodeVulnerabilityReviewContent(commentType ReviewCommentType, issue formats.SourceCodeRow, writer outputwriter.OutputWriter) (content string) { switch commentType { case IacComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent( - issue.Severity, - issue.Finding, - issue.ScannerDescription, - writer, - ), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(issue, false ,writer), writer) case SastComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent( - issue.Severity, - issue.Finding, - issue.ScannerDescription, - issue.CodeFlow, - writer, - ), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(issue, false ,writer), writer) case SecretComment: - applicability := "" - if issue.Applicability != nil { - applicability = issue.Applicability.Status - } - return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent( - issue.Severity, - issue.IssueId, - issue.Finding, - issue.ScannerDescription, - applicability, - writer, - ), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(issue, false ,writer), writer) + } + return +} + +func generateSourceCodeViolationReviewContent(commentType ReviewCommentType, issue formats.SourceCodeRow, writer outputwriter.OutputWriter) (content string) { + switch commentType { + case IacComment: + return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(issue, true, writer), writer) + case SastComment: + return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(issue, true, writer), writer) + case SecretComment: + return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(issue, true, writer), writer) } return } diff --git a/utils/consts.go b/utils/consts.go index 7f5e919fb..658dbccef 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -57,6 +57,7 @@ const ( PathExclusionsEnv = "JF_PATH_EXCLUSIONS" jfrogWatchesEnv = "JF_WATCHES" jfrogProjectEnv = "JF_PROJECT" + ViolationContextEnv = "JF_VIOLATION_CONTEXT" IncludeAllVulnerabilitiesEnv = "JF_INCLUDE_ALL_VULNERABILITIES" AvoidPreviousPrCommentsDeletionEnv = "JF_AVOID_PREVIOUS_PR_COMMENTS_DELETION" FailOnSecurityIssuesEnv = "JF_FAIL" diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index 42c78933c..71c834196 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -7,6 +7,7 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/severityutils" ) + // TODO: after refactor, move this to security-cli as a new formats or remove this and use the existing formats // Group issues by scan type type ScansIssuesCollection struct { @@ -173,20 +174,36 @@ func (ic *ScansIssuesCollection) SastIssuesExists() bool { return len(ic.SastVulnerabilities) > 0 || len(ic.SastViolations) > 0 } -func (ic *ScansIssuesCollection) GetTotalIssues() int { - return ic.GetTotalVulnerabilities() + ic.GetTotalViolations() +func (ic *ScansIssuesCollection) GetTotalIssues(includeSecrets bool) int { + return ic.GetTotalVulnerabilities(includeSecrets) + ic.GetTotalViolations(includeSecrets) +} + +func (ic *ScansIssuesCollection) GetApplicableEvidences() []formats.Evidence { + } // Violations -func (ic *ScansIssuesCollection) GetTotalViolations() int { - return len(ic.ScaViolations) + len(ic.IacViolations) + len(ic.SecretsViolations) + len(ic.SastViolations) + len(ic.LicensesViolations) +func (ic *ScansIssuesCollection) GetTotalViolations(includeSecrets bool) int { + total := ic.GetTotalScaViolations() + len(ic.IacViolations) + len(ic.SastViolations) + if includeSecrets { + total += len(ic.SecretsViolations) + } + return total +} + +func (ic *ScansIssuesCollection) GetTotalScaViolations() int { + return len(ic.ScaViolations) + len(ic.LicensesViolations) } // Vulnerabilities -func (ic *ScansIssuesCollection) GetTotalVulnerabilities() int { - return len(ic.ScaVulnerabilities) + len(ic.IacVulnerabilities) + len(ic.SecretsVulnerabilities) + len(ic.SastVulnerabilities) +func (ic *ScansIssuesCollection) GetTotalVulnerabilities(includeSecrets bool) int { + total := len(ic.ScaVulnerabilities) + len(ic.IacVulnerabilities) + len(ic.SastVulnerabilities) + if includeSecrets { + total += len(ic.SecretsVulnerabilities) + } + return total } diff --git a/utils/outputwriter/icons.go b/utils/outputwriter/icons.go index 3a3892e5b..3e2326a8d 100644 --- a/utils/outputwriter/icons.go +++ b/utils/outputwriter/icons.go @@ -27,6 +27,12 @@ const ( notApplicableLowSeveritySource ImageSource = "v2/notApplicableLow.png" unknownSeveritySource ImageSource = "v2/applicableUnknownSeverity.png" notApplicableUnknownSeveritySource ImageSource = "v2/notApplicableUnknown.png" + + smallCriticalSeveritySource ImageSource = "v2/smallCritical.svg" + smallHighSeveritySource ImageSource = "v2/smallHigh.svg" + smallMediumSeveritySource ImageSource = "v2/smallMedium.svg" + smallLowSeveritySource ImageSource = "v2/smallLow.svg" + smallUnknownSeveritySource ImageSource = "v2/smallUnknown.svg" ) func getSeverityTag(iconName IconName, applicability string) string { @@ -36,6 +42,10 @@ func getSeverityTag(iconName IconName, applicability string) string { return getApplicableIconTags(iconName) } +func getSmallSeverityTag(iconName IconName) string { + return getSmallApplicableIconTags(iconName) +} + func getNotApplicableIconTags(iconName IconName) string { switch strings.ToLower(string(iconName)) { case "critical": @@ -64,6 +74,20 @@ func getApplicableIconTags(iconName IconName) string { return GetIconTag(unknownSeveritySource) + "
" } +func getSmallApplicableIconTags(iconName IconName) string { + switch strings.ToLower(string(iconName)) { + case "critical": + return GetIconTag(smallCriticalSeveritySource) + " " + case "high": + return GetIconTag(smallHighSeveritySource) + " " + case "medium": + return GetIconTag(smallMediumSeveritySource) + " " + case "low": + return GetIconTag(smallLowSeveritySource) + " " + } + return GetIconTag(smallUnknownSeveritySource) + " " +} + func GetBanner(banner ImageSource) string { return GetMarkdownCenterTag(MarkAsLink(GetIconTag(banner), FrogbotDocumentationUrl)) } diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 64b37e4e0..b128dfc0b 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -19,18 +19,18 @@ const ( FrogbotDocumentationUrl = "https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot" ReviewCommentId = "FrogbotReviewComment" - scanSummaryTitle = "📊 Scan Summary" + scanSummaryTitle = "📗 Scan Summary" + issuesDetailsSubTitle = "🔖 Details" + jfrogResearchDetailsSubTitle = "🔬 JFrog Research Details" - policyViolationTitle = "🚥 Policy Violations" - securityViolationTitle = "🚨 Security Violations" - licenseViolationTitle = "📜 License Violations" - violationsDetailsSubTitle = "🔖 Details" + policyViolationTitle = "🚥 Policy Violations" + securityViolationTitle = "🚨 Security Violations" + licenseViolationTitle = "⚖️ License Violations" - vulnerableDependenciesTitle = "📦 Vulnerable Dependencies" - vulnerableDependenciesResearchDetailsSubTitle = "🔬 Research Details" + vulnerableDependenciesTitle = "📦 Vulnerable Dependencies" //#nosec G101 -- not a secret - secretsTitle = "🗝️ Secret" + secretsTitle = "🤫 Secret" contextualAnalysisTitle = "📦🔍 Contextual Analysis CVE" iacTitle = "🛠️ Infrastructure as Code" sastTitle = "🎯 Static Application Security Testing (SAST)" @@ -41,6 +41,44 @@ var ( jasFeaturesMsgWhenNotEnabled = MarkAsBold("Frogbot") + " also supports " + MarkAsBold("Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning") + ". This features are included as part of the " + MarkAsLink("JFrog Advanced Security", "https://jfrog.com/advanced-security") + " package, which isn't enabled on your system." ) +// For review comment Frogbot creates on Scan PR +func GenerateReviewCommentContent(content string, writer OutputWriter) string { + var contentBuilder strings.Builder + contentBuilder.WriteString(MarkdownComment(ReviewCommentId)) + customCommentTitle := writer.PullRequestCommentTitle() + if customCommentTitle != "" { + WriteContent(&contentBuilder, writer.MarkAsTitle(MarkAsBold(customCommentTitle), 2)) + } + WriteContent(&contentBuilder, content, footer(writer)) + return contentBuilder.String() +} + +// When can't create review comment, create a fallback comment by adding the location description to the content as a prefix +func GetFallbackReviewCommentContent(content string, location formats.Location, writer OutputWriter) string { + var contentBuilder strings.Builder + contentBuilder.WriteString(MarkdownComment(ReviewCommentId)) + WriteContent(&contentBuilder, getFallbackCommentLocationDescription(location), content) + return contentBuilder.String() +} + +func IsFrogbotComment(content string) bool { + return strings.Contains(content, ReviewCommentId) +} + +func getFallbackCommentLocationDescription(location formats.Location) string { + return fmt.Sprintf("%s\nat %s (line %d)", MarkAsCodeSnippet(location.Snippet), MarkAsQuote(location.File), location.StartLine) +} + +// Summary comment, including banner, footer wrapping the content with a decorator +func GetMainCommentContent(contentForComments []string, issuesExists, isComment bool, writer OutputWriter) (comments []string) { + return ConvertContentToComments(contentForComments, writer, func(commentCount int, content string) string { + if commentCount == 0 { + content = GetPRSummaryMainCommentDecorator(issuesExists, isComment, writer)(commentCount, content) + } + return GetFrogbotCommentBaseDecorator(writer)(commentCount, content) + }) +} + // Adding markdown prefix to identify Frogbot comment and a footer with the link to the documentation func GetFrogbotCommentBaseDecorator(writer OutputWriter) CommentDecorator { return func(_ int, content string) string { @@ -68,15 +106,6 @@ func GetPRSummaryMainCommentDecorator(issuesExists, isComment bool, writer Outpu } } -func GetPRSummaryContent(contentForComments []string, issuesExists, isComment bool, writer OutputWriter) (comments []string) { - return ConvertContentToComments(contentForComments, writer, func(commentCount int, content string) string { - if commentCount == 0 { - content = GetPRSummaryMainCommentDecorator(issuesExists, isComment, writer)(commentCount, content) - } - return GetFrogbotCommentBaseDecorator(writer)(commentCount, content) - }) -} - func getPRSummaryBanner(issuesExists, isComment bool, provider vcsutils.VcsProvider) ImageSource { if !isComment { return fixCVETitleSrc(provider) @@ -87,15 +116,6 @@ func getPRSummaryBanner(issuesExists, isComment bool, provider vcsutils.VcsProvi return PRSummaryCommentTitleSrc(provider) } -// TODO: remove this at the next release, it's not used anymore and replaced by adding ReviewCommentId comment to the content -func IsFrogbotSummaryComment(writer OutputWriter, content string) bool { - client := writer.VcsProvider() - return strings.Contains(content, GetBanner(NoIssuesTitleSrc(client))) || - strings.Contains(content, GetSimplifiedTitle(NoIssuesTitleSrc(client))) || - strings.Contains(content, GetBanner(PRSummaryCommentTitleSrc(client))) || - strings.Contains(content, GetSimplifiedTitle(PRSummaryCommentTitleSrc(client))) -} - func NoIssuesTitleSrc(vcsProvider vcsutils.VcsProvider) ImageSource { if vcsProvider == vcsutils.GitLab { return NoVulnerabilityMrBannerSource @@ -130,30 +150,34 @@ func footer(writer OutputWriter) string { // Summary content -func scanSummaryContent(issues issues.ScansIssuesCollection, violations bool, writer OutputWriter) string { +func ScanSummaryContent(issues issues.ScansIssuesCollection, violationContext string, includeSecrets bool, writer OutputWriter) string { + if !issues.IssuesExists(includeSecrets) { + return "" + } var contentBuilder strings.Builder - issueType := "vulnerabilities" - totalIssues := issues.GetTotalVulnerabilities() - if violations { - issueType = "violations" - totalIssues = issues.GetTotalViolations() + totalIssues := issues.GetTotalVulnerabilities(includeSecrets) + violations := false + if violationContext != "" { + totalIssues = issues.GetTotalViolations(includeSecrets) + violations = true } // Title WriteContent(&contentBuilder, writer.MarkAsTitle(scanSummaryTitle, 2), - MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", issueType, totalIssues)), + MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", getIssueType(violations), totalIssues)), ) // Create table, a row for each sub scans summary + secretsDetails := "" + if includeSecrets { + secretsDetails = getScanSecurityIssuesDetails(issues, utils.SecretsScan, violations, writer) + } table := NewMarkdownTable("Scan Category", "Status", "Security Issues") table.AddRow(MarkAsBold("Software Composition Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.ScaScan, violations, writer)) table.AddRow(MarkAsBold("Contextual Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ContextualAnalysisScan)), "") table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.SastScan, violations, writer)) - table.AddRow(MarkAsBold("Secrets"), getSubScanResultStatus(issues.GetScanStatus(utils.SecretsScan)), getScanSecurityIssuesDetails(issues, utils.SecretsScan, violations, writer)) + table.AddRow(MarkAsBold("Secrets"), getSubScanResultStatus(issues.GetScanStatus(utils.SecretsScan)), secretsDetails) table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), getSubScanResultStatus(issues.GetScanStatus(utils.IacScan)), getScanSecurityIssuesDetails(issues, utils.IacScan, violations, writer)) WriteContent(&contentBuilder, writer.MarkInCenter(table.Build())) - // link to the scan results in JFrog - // WriteNewLine(&contentBuilder) - // WriteContent(&contentBuilder, writer.MarkInCenter(MarkAsLink("See the results of the scan in JFrog", "an-url"))) return contentBuilder.String() } @@ -220,18 +244,16 @@ func toSeverityDetails(severities map[severityutils.Severity]int) string { return contentBuilder.String() } -// Policy Violations +// SCA (Policy) Violations // Summary content for the security violations that we can't yet have location on (SCA, License) -func PolicyViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (content []string) { - if issues.GetTotalViolations() == 0 { +func PolicyViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (policyViolationContent []string) { + if issues.GetTotalScaViolations() == 0 { return []string{} } - // Violations Summary - content = append(content, scanSummaryContent(issues, true, writer)) - // Policy Violations Content - policyViolationContent := append(getSecurityViolationsContent(issues, writer), getLicenseViolationsContent(issues, writer)...) - return append(content, ConvertContentToComments(policyViolationContent, writer, getDecoratorWithPolicyViolationTitle(writer))...) + policyViolationContent = append(policyViolationContent, getSecurityViolationsContent(issues, writer)...) + policyViolationContent = append(policyViolationContent, getLicenseViolationsContent(issues, writer)...) + return ConvertContentToComments(policyViolationContent, writer, getDecoratorWithPolicyViolationTitle(writer)) } func getDecoratorWithPolicyViolationTitle(writer OutputWriter) func(int, string) string { @@ -247,8 +269,11 @@ func getDecoratorWithPolicyViolationTitle(writer OutputWriter) func(int, string) // Security Violations func getSecurityViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (content []string) { + if len(issues.ScaViolations) == 0 { + return []string{} + } content = append(content, getSecurityViolationsSummaryTable(issues.ScaViolations, writer)) - content = append(content, getSecurityViolationsDetailsContent(issues.ScaViolations, writer)...) + content = append(content, getScaSecurityIssueDetailsContent(issues.ScaViolations, true, writer)...) return ConvertContentToComments(content, writer, getDecoratorWithSecurityViolationTitle(writer)) } @@ -277,101 +302,26 @@ func getSecurityViolationsSummaryTable(violations []formats.VulnerabilityOrViola } // Construct rows for _, violation := range violations { - row := []CellData{{writer.FormattedSeverity(violation.Severity, violation.Applicable)}} + row := []CellData{{writer.FormattedSeverity(violation.Severity, violation.Applicable, false)}, getCveIdsCellData(violation.Cves, violation.IssueId)} if writer.IsShowingCaColumn() { row = append(row, NewCellData(violation.Applicable)) } row = append(row, - getDirectDependenciesCellData("%s:%s", violation.Components), - NewCellData(fmt.Sprintf("%s %s", violation.ImpactedDependencyName, violation.ImpactedDependencyVersion)), - NewCellData(violation.FixedVersions...), - getCveIdsCellData(violation.Cves, violation.IssueId), + getDirectDependenciesCellData(violation.Components), + NewCellData(fmt.Sprintf("%s:%s", violation.ImpactedDependencyName, violation.ImpactedDependencyVersion)), + NewCellData(violation.Watch), ) table.AddRowWithCellData(row...) } return writer.MarkInCenter(table.Build()) } -func getImpactedComponentLocationIfDirectDependency(impactedComponent formats.ImpactedDependencyDetails) string { - component := getComponentIfDirect(impactedComponent) - if component != nil && component.Location != nil { - return component.Location.File - } - return "" -} - -func getComponentIfDirect(impactedComponent formats.ImpactedDependencyDetails) (component *formats.ComponentRow) { - for _, c := range impactedComponent.Components { - // Check if the impacted component is a direct dependency - if c.Name == impactedComponent.ImpactedDependencyName && c.Version == impactedComponent.ImpactedDependencyVersion { - return &c - } - } - return -} - -func getSecurityViolationsDetailsContent(violations []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { - if len(violations) == 0 { - return - } - for _, violation := range violations { - if len(violations) == 1 { - content = append(content, getScaSecurityViolationDetails(violation, writer)) - } else { - content = append(content, writer.MarkAsDetails(getViolationDescriptionIdentifier(violation), 5, getScaSecurityViolationDetails(violation, writer))) - } - } - // Split content if it exceeds the size limit and decorate it with title - return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { - contentBuilder := strings.Builder{} - WriteContent(&contentBuilder, writer.MarkAsTitle(violationsDetailsSubTitle, 4)) - WriteContent(&contentBuilder, detailsContent) - return contentBuilder.String() - }) -} - -func getViolationDescriptionIdentifier(violation formats.VulnerabilityOrViolationRow) string { - return fmt.Sprintf(`%s %s %s (%s)`, getVulnerabilityDescriptionIdentifier(violation.Cves, violation.IssueId), violation.ImpactedDependencyName, violation.ImpactedDependencyVersion, violation.Watch) -} - -func getScaSecurityViolationDetails(violation formats.VulnerabilityOrViolationRow, writer OutputWriter) (content string) { - directComponent := []string{} - for _, component := range violation.ImpactedDependencyDetails.Components { - directComponent = append(directComponent, fmt.Sprintf("%s:%s", component.Name, component.Version)) - } - table := getBaseDependencyViolationDetailsTable( - directComponent, - fmt.Sprintf("%s:%s", violation.ImpactedDependencyName, violation.ImpactedDependencyVersion), - violation.Severity, - "Security", - violation.Watch, - violation.Policies, - writer, - ) - if writer.IsShowingCaColumn() { - table.AddRow(MarkAsBold("Contextual Analysis:"), violation.Applicable) - } - if violation.JfrogResearchInformation != nil { - table.AddRow(MarkAsBold("Jfrog Research Severity:"), violation.JfrogResearchInformation.Severity) - } - if len(violation.FixedVersions) > 0 { - table.AddRow(MarkAsBold("Fixed Versions:"), strings.Join(violation.FixedVersions, ", ")) - } - if len(violation.Cves) > 0 { - cvssV3 := []string{} - for _, cve := range violation.Cves { - cvssV3 = append(cvssV3, cve.CvssV3) - } - table.AddRow(MarkAsBold("CVSS V3:"), strings.Join(cvssV3, ", ")) - } - var contentBuilder strings.Builder - WriteContent(&contentBuilder, table.Build(), fmt.Sprintf("%s: %s", MarkAsBold("Description"), violation.Summary)) - return contentBuilder.String() -} - // License violations func getLicenseViolationsContent(issues issues.ScansIssuesCollection, writer OutputWriter) (content []string) { + if len(issues.LicensesViolations) == 0 { + return []string{} + } content = append(content, getLicenseViolationsSummaryTable(issues.LicensesViolations, writer)) content = append(content, getLicenseViolationsDetailsContent(issues.LicensesViolations, writer)...) return ConvertContentToComments(content, writer, getDecoratorWithLicenseViolationTitle(writer)) @@ -388,14 +338,7 @@ func getDecoratorWithLicenseViolationTitle(writer OutputWriter) func(int, string } func getLicenseViolationsSummaryTable(licenses []formats.LicenseViolationRow, writer OutputWriter) string { - if len(licenses) == 0 { - return "" - } - // Title - var contentBuilder strings.Builder - WriteContent(&contentBuilder, writer.MarkAsTitle("⚖️ Violated Licenses", 2)) - // Content - table := NewMarkdownTable("Severity", "ID", "Direct Dependencies", "Impacted Dependency", "Watch Name").SetDelimiter(writer.Separator()) + table := NewMarkdownTable("Severity", "License", "Direct Dependencies", "Impacted Dependency", "Watch Name").SetDelimiter(writer.Separator()) if _, ok := writer.(*SimplifiedOutput); ok { // The values in this cell can be potentially large, since SimplifiedOutput does not support tags, we need to show each value in a separate row. // It means that the first row will show the full details, and the following rows will show only the direct dependency. @@ -404,283 +347,102 @@ func getLicenseViolationsSummaryTable(licenses []formats.LicenseViolationRow, wr } for _, license := range licenses { table.AddRowWithCellData( - NewCellData(license.Severity), + NewCellData(writer.FormattedSeverity(license.Severity, "Applicable", false)), NewCellData(license.LicenseKey), - getDirectDependenciesCellData("%s %s", license.Components), - NewCellData(fmt.Sprintf("%s %s", license.ImpactedDependencyName, license.ImpactedDependencyVersion)), + getDirectDependenciesCellData(license.Components), + NewCellData(fmt.Sprintf("%s:%s", license.ImpactedDependencyName, license.ImpactedDependencyVersion)), NewCellData(license.Watch), ) } - WriteContent(&contentBuilder, writer.MarkInCenter(table.Build())) - return contentBuilder.String() + return writer.MarkInCenter(table.Build()) } func getLicenseViolationsDetailsContent(licenseViolations []formats.LicenseViolationRow, writer OutputWriter) (content []string) { if len(licenseViolations) == 0 { return } - // for _, violation := range licenseViolations { - // if len(licenseViolations) == 1 { - // content = append(content, getScaLicenseViolationDetails(violation, writer)) - // } else { - // content = append(content, writer.MarkAsDetails(getViolationDescriptionIdentifier(violation), 5, getScaLicenseViolationDetails(violation, writer))) - // } - // } + for _, violation := range licenseViolations { + if len(licenseViolations) == 1 { + content = append(content, getScaLicenseViolationDetails(violation, writer)) + } else { + content = append(content, writer.MarkAsDetails( + getComponentIssueIdentifier(violation.LicenseKey, violation.ImpactedDependencyName, violation.ImpactedDependencyVersion, violation.Watch), 4, + getScaLicenseViolationDetails(violation, writer), + )) + } + } // Split content if it exceeds the size limit and decorate it with title return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { contentBuilder := strings.Builder{} - WriteContent(&contentBuilder, writer.MarkAsTitle(violationsDetailsSubTitle, 4)) + WriteContent(&contentBuilder, writer.MarkAsTitle(issuesDetailsSubTitle, 3)) WriteContent(&contentBuilder, detailsContent) return contentBuilder.String() }) } -func getJasSecurityViolationDetails(severity, violationType, watch string, policies []string, writer OutputWriter) string { - table := getBaseViolationDetailsTable(severity, violationType, watch, policies, writer) - - return table.Build() -} - -func getSecretsSecurityViolationDetails(severity, violationType, watch string, policies []string, writer OutputWriter) string { - // table := getBaseDependencyViolationDetailsTable(severity, violationType, watch, policies, writer) - - // return table.Build() - return "" -} - -func getBaseViolationDetailsTable(severity, violationType, watch string, policies []string, writer OutputWriter) *MarkdownTableBuilder { +func getScaLicenseViolationDetails(violation formats.LicenseViolationRow, writer OutputWriter) (content string) { noHeaderTable := NewMarkdownTable("", "") - noHeaderTable.AddRow(MarkAsBold("Violation Severity:"), severity) - noHeaderTable.AddRow(MarkAsBold("Type:"), violationType) - noHeaderTable.AddRow(MarkAsBold("Policies:"), strings.Join(policies, ", ")) - noHeaderTable.AddRow(MarkAsBold("Watch name:"), watch) - - return noHeaderTable -} - -func getBaseDependencyViolationDetailsTable(direct []string, impacted, severity, violationType, watch string, policies []string, writer OutputWriter) *MarkdownTableBuilder { - noHeaderTable := getBaseViolationDetailsTable(severity, violationType, watch, policies, writer) - - noHeaderTable.AddRow(MarkAsBold("Direct Dependency:"), strings.Join(direct, ", ")) - noHeaderTable.AddRow(MarkAsBold("Impacted Dependency:"), impacted) + if len(violation.Policies) > 0 { + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(violation.Policies...)) + } + noHeaderTable.AddRow(MarkAsBold("Full Name:"), violation.LicenseName) - return noHeaderTable + return noHeaderTable.Build() } -// func getBaseJasViolationDetailsTable(ruleId, file, line, severity, violationType, watch string, policies []string, writer OutputWriter) *MarkdownTableBuilder { -// noHeaderTable := getBaseViolationDetailsTable(severity, violationType, watch, policies, writer) +// Sca Vulnerabilities -// noHeaderTable.AddRow(MarkAsBold("Rule ID:"), ruleId) -// noHeaderTable.AddRow(MarkAsBold("File Path:"), file) -// noHeaderTable.AddRow(MarkAsBold("Line:"), line) - -// return noHeaderTable -// } - -func VulnerabilitiesContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { +func GetVulnerabilitiesContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { if len(vulnerabilities) == 0 { return []string{} } - content = append(content, writer.MarkAsTitle(vulnerableDependenciesTitle, 2)) - content = append(content, vulnerabilitiesSummaryContent(vulnerabilities, writer)) - content = append(content, vulnerabilityDetailsContent(vulnerabilities, writer)...) - return + content = append(content, writer.MarkInCenter(getVulnerabilitiesSummaryTable(vulnerabilities, writer))) + content = append(content, getScaSecurityIssueDetailsContent(vulnerabilities, false, writer)...) + return ConvertContentToComments(content, writer, getDecoratorWithScaVulnerabilitiesTitle(writer)) } -func vulnerabilitiesSummaryContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { - var contentBuilder strings.Builder - WriteContent(&contentBuilder, - writer.MarkAsTitle("✍️ Summary", 3), - writer.MarkInCenter(getVulnerabilitiesSummaryTable(vulnerabilities, writer)), - ) - return contentBuilder.String() +func getDecoratorWithScaVulnerabilitiesTitle(writer OutputWriter) func(int, string) string { + return func(commentCount int, content string) string { + contentBuilder := strings.Builder{} + // Decorate each part of the split content with a title as prefix and return the content + WriteContent(&contentBuilder, writer.MarkAsTitle(vulnerableDependenciesTitle, 3)) + WriteContent(&contentBuilder, content) + return contentBuilder.String() + } } func getVulnerabilitiesSummaryTable(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { // Construct table - columns := []string{"SEVERITY"} + columns := []string{"Severity", "ID"} if writer.IsShowingCaColumn() { - columns = append(columns, "CONTEXTUAL ANALYSIS") + columns = append(columns, "Contextual Analysis") } - columns = append(columns, "DIRECT DEPENDENCIES", "IMPACTED DEPENDENCY", "FIXED VERSIONS", "CVES") + columns = append(columns, "Direct Dependencies", "Impacted Dependency", "Fixed Versions") table := NewMarkdownTable(columns...).SetDelimiter(writer.Separator()) if _, ok := writer.(*SimplifiedOutput); ok { // The values in this cell can be potentially large, since SimplifiedOutput does not support tags, we need to show each value in a separate row. // It means that the first row will show the full details, and the following rows will show only the direct dependency. // It makes it easier to read the table and less crowded with text in a single cell that could be potentially large. - table.GetColumnInfo("DIRECT DEPENDENCIES").ColumnType = MultiRowColumn + table.GetColumnInfo("Direct Dependencies").ColumnType = MultiRowColumn } // Construct rows for _, vulnerability := range vulnerabilities { - row := []CellData{{writer.FormattedSeverity(vulnerability.Severity, vulnerability.Applicable)}} + row := []CellData{{writer.FormattedSeverity(vulnerability.Severity, vulnerability.Applicable, false)}, getCveIdsCellData(vulnerability.Cves, vulnerability.IssueId)} if writer.IsShowingCaColumn() { row = append(row, NewCellData(vulnerability.Applicable)) } row = append(row, - getDirectDependenciesCellData("%s:%s", vulnerability.Components), + getDirectDependenciesCellData(vulnerability.Components), NewCellData(fmt.Sprintf("%s %s", vulnerability.ImpactedDependencyName, vulnerability.ImpactedDependencyVersion)), NewCellData(vulnerability.FixedVersions...), - getCveIdsCellData(vulnerability.Cves, vulnerability.IssueId), ) table.AddRowWithCellData(row...) } return table.Build() } -func getDirectDependenciesCellData(format string, components []formats.ComponentRow) (dependencies CellData) { - if len(components) == 0 { - return NewCellData() - } - for _, component := range components { - dependencies = append(dependencies, fmt.Sprintf(format, component.Name, component.Version)) - } - return -} - -func getCveIdsCellData(cveRows []formats.CveRow, issueId string) (ids CellData) { - if len(cveRows) == 0 { - return NewCellData(issueId) - } - for _, cve := range cveRows { - ids = append(ids, cve.Id) - } - return -} - -type vulnerabilityOrViolationDetails struct { - details string - title string - dependencyName string - dependencyVersion string -} - -func vulnerabilityDetailsContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { - vulnerabilitiesWithDetails := getVulnerabilityWithDetails(vulnerabilities) - if len(vulnerabilitiesWithDetails) == 0 { - return - } - // Prepare content for each vulnerability details - for i := range vulnerabilitiesWithDetails { - if len(vulnerabilitiesWithDetails) == 1 { - content = append(content, vulnerabilitiesWithDetails[i].details) - } else { - content = append(content, writer.MarkAsDetails( - fmt.Sprintf(`%s %s %s`, vulnerabilitiesWithDetails[i].title, - vulnerabilitiesWithDetails[i].dependencyName, - vulnerabilitiesWithDetails[i].dependencyVersion), - 4, vulnerabilitiesWithDetails[i].details, - )) - } - } - // Split content if it exceeds the size limit and decorate it with title - return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { - contentBuilder := strings.Builder{} - WriteContent(&contentBuilder, writer.MarkAsTitle(vulnerableDependenciesResearchDetailsSubTitle, 3)) - WriteContent(&contentBuilder, detailsContent) - return contentBuilder.String() - }) -} - -func getVulnerabilityWithDetails(vulnerabilities []formats.VulnerabilityOrViolationRow) (vulnerabilitiesWithDetails []vulnerabilityOrViolationDetails) { - for i := range vulnerabilities { - vulDescriptionContent := createVulnerabilityResearchDescription(&vulnerabilities[i]) - if vulDescriptionContent == "" { - // No content - continue - } - vulnerabilitiesWithDetails = append(vulnerabilitiesWithDetails, vulnerabilityOrViolationDetails{ - details: vulDescriptionContent, - title: getVulnerabilityDescriptionIdentifier(vulnerabilities[i].Cves, vulnerabilities[i].IssueId), - dependencyName: vulnerabilities[i].ImpactedDependencyName, - dependencyVersion: vulnerabilities[i].ImpactedDependencyVersion, - }) - } - return -} - -func createVulnerabilityResearchDescription(vulnerability *formats.VulnerabilityOrViolationRow) string { - var descriptionBuilder strings.Builder - vulnResearch := vulnerability.JfrogResearchInformation - if vulnResearch == nil { - vulnResearch = &formats.JfrogResearchInformation{Details: vulnerability.Summary} - } else if vulnResearch.Details == "" { - vulnResearch.Details = vulnerability.Summary - } - - if vulnResearch.Details != "" { - WriteContent(&descriptionBuilder, MarkAsBold("Description:"), vulnResearch.Details) - } - if vulnResearch.Remediation != "" { - if vulnResearch.Details != "" { - WriteNewLine(&descriptionBuilder) - } - WriteContent(&descriptionBuilder, MarkAsBold("Remediation:"), vulnResearch.Remediation) - } - return descriptionBuilder.String() -} - -func getVulnerabilityDescriptionIdentifier(cveRows []formats.CveRow, xrayId string) string { - identifier := results.GetIssueIdentifier(cveRows, xrayId, ", ") - if identifier == "" { - return "" - } - return fmt.Sprintf("[ %s ]", identifier) -} - -func LicensesContent(licenses []formats.LicenseViolationRow, writer OutputWriter) string { - if len(licenses) == 0 { - return "" - } - // Title - var contentBuilder strings.Builder - WriteContent(&contentBuilder, writer.MarkAsTitle("⚖️ Violated Licenses", 2)) - // Content - table := NewMarkdownTable("SEVERITY", "LICENSE", "DIRECT DEPENDENCIES", "IMPACTED DEPENDENCY").SetDelimiter(writer.Separator()) - for _, license := range licenses { - table.AddRowWithCellData( - NewCellData(license.Severity), - NewCellData(license.LicenseKey), - getDirectDependenciesCellData("%s %s", license.Components), - NewCellData(fmt.Sprintf("%s %s", license.ImpactedDependencyName, license.ImpactedDependencyVersion)), - ) - } - WriteContent(&contentBuilder, writer.MarkInCenter(table.Build())) - return contentBuilder.String() -} - -// For review comment Frogbot creates on Scan PR -func GenerateReviewCommentContent(content string, writer OutputWriter) string { - var contentBuilder strings.Builder - contentBuilder.WriteString(MarkdownComment(ReviewCommentId)) - customCommentTitle := writer.PullRequestCommentTitle() - if customCommentTitle != "" { - WriteContent(&contentBuilder, writer.MarkAsTitle(MarkAsBold(customCommentTitle), 2)) - } - WriteContent(&contentBuilder, content, footer(writer)) - return contentBuilder.String() -} - -// When can't create review comment, create a fallback comment by adding the location description to the content as a prefix -func GetFallbackReviewCommentContent(content string, location formats.Location, writer OutputWriter) string { - var contentBuilder strings.Builder - contentBuilder.WriteString(MarkdownComment(ReviewCommentId)) - WriteContent(&contentBuilder, getFallbackCommentLocationDescription(location), content) - return contentBuilder.String() -} - -func IsFrogbotComment(content string) bool { - return strings.Contains(content, ReviewCommentId) -} - -func getFallbackCommentLocationDescription(location formats.Location) string { - return fmt.Sprintf("%s\nat %s (line %d)", MarkAsCodeSnippet(location.Snippet), MarkAsQuote(location.File), location.StartLine) -} - -func GetApplicabilityDescriptionTable(severity, cve, impactedDependency, finding string, writer OutputWriter) string { - table := NewMarkdownTable("Severity", "Impacted Dependency", "Finding", "CVE").AddRow(writer.FormattedSeverity(severity, "Applicable"), impactedDependency, finding, cve) - return table.Build() -} +// Applicable CVE Evidence func ApplicableCveReviewContent(severity, finding, fullDetails, cve, cveDetails, impactedDependency, remediation string, writer OutputWriter) string { var contentBuilder strings.Builder @@ -690,53 +452,92 @@ func ApplicableCveReviewContent(severity, finding, fullDetails, cve, cveDetails, writer.MarkAsDetails("Description", 3, fullDetails), writer.MarkAsDetails("CVE details", 3, cveDetails), ) - if len(remediation) > 0 { WriteContent(&contentBuilder, writer.MarkAsDetails("Remediation", 3, remediation)) } return contentBuilder.String() } +func GetApplicabilityDescriptionTable(severity, cve, impactedDependency, finding string, writer OutputWriter) string { + table := NewMarkdownTable("Severity", "Impacted Dependency", "Finding", "CVE").AddRow(writer.FormattedSeverity(severity, "Applicable", false), impactedDependency, finding, cve) + return table.Build() +} + // JAS -func getJasDescriptionTable(severity, finding string, writer OutputWriter) string { - return NewMarkdownTable("Severity", "Finding").AddRow(writer.FormattedSeverity(severity, jasutils.Applicable.String()), finding).Build() +func getJasIssueDescriptionTable(issue formats.SourceCodeRow, writer OutputWriter) string { + columns := []string{"Severity"} + rowData := []string{writer.FormattedSeverity(issue.Severity, "Applicable", false)} + // Optional issueId column (as stored at the platform) + if issue.IssueId != "" { + columns = append(columns, "ID") + rowData = append(rowData, issue.IssueId) + } + columns = append(columns, "Finding") + rowData = append(rowData, issue.Finding) + return NewMarkdownTable(columns...).AddRow(rowData...).Build() } -func SecretReviewContent(severity, issueId, finding, fullDetails, applicability string, writer OutputWriter) string { +func getJasFullDescription(issue formats.SourceCodeRow, violation bool, issueDescTable string, writer OutputWriter) string { var contentBuilder strings.Builder - WriteContent(&contentBuilder, - writer.MarkAsTitle(fmt.Sprintf("%s Vulnerability", secretsTitle), 2), - writer.MarkInCenter(getSecretsDescriptionTable(severity, issueId, finding, applicability, writer)), - writer.MarkAsDetails("Full description", 3, fullDetails), - ) + // Write the vulnerability/violation details + WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%s Details", getIssueType(violation)), 4, issueDescTable)) + // Separator + WriteNewLine(&contentBuilder) + // Write the description + WriteContent(&contentBuilder, issue.ScannerDescription) return contentBuilder.String() } -func IacReviewContent(severity, finding, fullDetails string, writer OutputWriter) string { +func getBaseJasDetailsTable(watch string, policies, cwe []string, writer OutputWriter) *MarkdownTableBuilder { + noHeaderTable := NewMarkdownTable("", "").SetDelimiter(writer.Separator()) + // For Violations + if len(policies) > 0 { + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(policies...)) + } + if watch != "" { + noHeaderTable.AddRow(MarkAsBold("Watch Name:"), watch) + } + // General CWE attribute if exists + if len(cwe) > 0 { + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("CWE:")), NewCellData(cwe...)) + } + return noHeaderTable +} + +func IacReviewContent(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, - writer.MarkAsTitle(fmt.Sprintf("%s Vulnerability", iacTitle), 2), - writer.MarkInCenter(getJasDescriptionTable(severity, finding, writer)), - writer.MarkAsDetails("Full description", 3, fullDetails), + writer.MarkAsTitle(fmt.Sprintf("%s %s", iacTitle, getIssueType(violation)), 2), + writer.MarkInCenter(getJasIssueDescriptionTable(issue, writer)), + writer.MarkAsDetails("Full description", 3, getIacFullDescription(issue, violation, writer)), ) return contentBuilder.String() } -func SastReviewContent(severity, finding, fullDetails string, codeFlows [][]formats.Location, writer OutputWriter) string { +func getIacFullDescription(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { + return getJasFullDescription(issue, violation, getBaseJasDetailsTable(issue.Watch, issue.Policies, issue.CWE, writer).Build(), writer) +} + +func SastReviewContent(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, - writer.MarkAsTitle(fmt.Sprintf("%s Vulnerability", sastTitle), 2), - writer.MarkInCenter(getJasDescriptionTable(severity, finding, writer)), - writer.MarkAsDetails("Full description", 3, fullDetails), + writer.MarkAsTitle(fmt.Sprintf("%s %s", sastTitle, getIssueType(violation)), 2), + writer.MarkInCenter(getJasIssueDescriptionTable(issue, writer)), + writer.MarkAsDetails("Full description", 3, getSastFullDescription(issue, violation, writer)), ) - - if len(codeFlows) > 0 { - WriteContent(&contentBuilder, writer.MarkAsDetails("Code Flows", 3, sastCodeFlowsReviewContent(codeFlows, writer))) + if len(issue.CodeFlow) > 0 { + WriteContent(&contentBuilder, writer.MarkAsDetails("Code Flows", 3, sastCodeFlowsReviewContent(issue.CodeFlow, writer))) } return contentBuilder.String() } +func getSastFullDescription(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { + table := getBaseJasDetailsTable(issue.Watch, issue.Policies, issue.CWE, writer) + table.AddRow(MarkAsBold("Rule ID:"), issue.RuleId) + return getJasFullDescription(issue, violation, table.Build(), writer) +} + func sastCodeFlowsReviewContent(codeFlows [][]formats.Location, writer OutputWriter) string { var contentBuilder strings.Builder for _, flow := range codeFlows { @@ -753,42 +554,20 @@ func sastDataFlowLocationsReviewContent(flow []formats.Location) string { return contentBuilder.String() } -// Jas Violation - -func getJasViolationFullDescription(issue formats.SourceCodeRow, tableDetailsContent string, writer OutputWriter) string { - var contentBuilder strings.Builder - // Write the violation details - WriteContent(&contentBuilder, writer.MarkAsDetails("Violation Details", 4, tableDetailsContent)) - // Separator - WriteNewLine(&contentBuilder) - // Write the description - WriteContent(&contentBuilder, issue.ScannerDescription) - return contentBuilder.String() -} - -func IacViolationReviewContent(issue formats.SourceCodeRow, writer OutputWriter) string { +func SecretReviewContent(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { + applicability := "" + if issue.Applicability != nil { + applicability = issue.Applicability.Status + } var contentBuilder strings.Builder WriteContent(&contentBuilder, - writer.MarkAsTitle(fmt.Sprintf("%s Violation", iacTitle), 2), - writer.MarkInCenter(getJasIssueDescriptionTable(issue, writer)), - writer.MarkAsDetails("Full description", 3, getIacViolationFullDescription(issue, writer)), + writer.MarkAsTitle(fmt.Sprintf("%s %s", secretsTitle, getIssueType(violation)), 2), + writer.MarkInCenter(getSecretsDescriptionTable(issue.Severity, issue.IssueId, issue.Finding, applicability, writer)), + writer.MarkAsDetails("Full description", 3, getSecretsFullDescription(issue, violation, writer)), ) return contentBuilder.String() } -func getJasIssueDescriptionTable(issue formats.SourceCodeRow, writer OutputWriter) string { - columns := []string{"Severity"} - rowData := []string{writer.FormattedSeverity(issue.Severity, "Applicable")} - // Optional ID column - if issue.IssueId != "" { - columns = append(columns, "ID") - rowData = append(rowData, issue.IssueId) - } - columns = append(columns, "Finding") - rowData = append(rowData, issue.Finding) - return NewMarkdownTable(columns...).AddRow(rowData...).Build() -} - func getSecretsDescriptionTable(severity, issueId, finding, status string, writer OutputWriter) string { // Determine the issue applicable status applicability := jasutils.Applicable.String() @@ -798,7 +577,7 @@ func getSecretsDescriptionTable(severity, issueId, finding, status string, write } } columns := []string{"Severity"} - rowData := []string{writer.FormattedSeverity(severity, applicability)} + rowData := []string{writer.FormattedSeverity(severity, applicability, false)} // Determine if issueId is provided if issueId != "" { columns = append(columns, "ID") @@ -814,61 +593,238 @@ func getSecretsDescriptionTable(severity, issueId, finding, status string, write return NewMarkdownTable(columns...).AddRow(rowData...).Build() } -func getIacViolationFullDescription(issue formats.SourceCodeRow, writer OutputWriter) string { - return getJasViolationFullDescription(issue, getBaseJasViolationDetailsTable(issue.Watch, issue.Policies, []string{issue.CWE}, writer).Build(), writer) +func getSecretsFullDescription(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { + table := getBaseJasDetailsTable(issue.Watch, issue.Policies, issue.CWE, writer) + table.AddRow(MarkAsBold("Abbreviation:"), issue.RuleId) + return getJasFullDescription(issue, violation, table.Build(), writer) } -func getBaseJasViolationDetailsTable(watch string, policies, cwe []string, writer OutputWriter) *MarkdownTableBuilder { - noHeaderTable := NewMarkdownTable("", "").SetDelimiter(writer.Separator()) - if len(policies) > 0 { - noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(policies...)) +// Utilities + +func getIssueType(violation bool) string { + if violation { + return "Violation" } - if watch != "" { - noHeaderTable.AddRow(MarkAsBold("Watch Name:"), watch) + return "Vulnerability" +} +func getDirectDependenciesCellData(components []formats.ComponentRow) (dependencies CellData) { + if len(components) == 0 { + return NewCellData() } - if len(cwe) > 0 { - noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("CWE:")), NewCellData(cwe...)) + for _, component := range components { + dependencies = append(dependencies, fmt.Sprintf("%s:%s", component.Name, component.Version)) } - return noHeaderTable + return } -func SastViolationReviewContent(issue formats.SourceCodeRow, writer OutputWriter) string { - var contentBuilder strings.Builder - WriteContent(&contentBuilder, - writer.MarkAsTitle(fmt.Sprintf("%s Violation", sastTitle), 2), - writer.MarkInCenter(getJasIssueDescriptionTable(issue, writer)), - writer.MarkAsDetails("Full description", 3, getSastViolationFullDescription(issue, writer)), - ) - - if len(issue.CodeFlow) > 0 { - WriteContent(&contentBuilder, writer.MarkAsDetails("Code Flows", 3, sastCodeFlowsReviewContent(issue.CodeFlow, writer))) +func getCveIdsCellData(cveRows []formats.CveRow, issueId string) (ids CellData) { + if len(cveRows) == 0 { + return NewCellData(issueId) + } + for _, cve := range cveRows { + ids = append(ids, cve.Id) } + return +} - return contentBuilder.String() +func getScaSecurityIssueDetailsContent(issues []formats.VulnerabilityOrViolationRow, violations bool, writer OutputWriter) (content []string) { + issuesWithDetails := getIssuesWithDetails(issues) + if len(issuesWithDetails) == 0 { + return + } + for _, issue := range issuesWithDetails { + if len(issues) == 1 { + content = append(content, getScaSecurityIssueDetails(issue, violations, writer)) + } else { + content = append(content, writer.MarkAsDetails( + getComponentIssueIdentifier(results.GetIssueIdentifier(issue.Cves, issue.IssueId, ", "), issue.ImpactedDependencyName, issue.ImpactedDependencyVersion, issue.Watch), 4, + getScaSecurityIssueDetails(issue, violations, writer), + )) + } + } + // Split content if it exceeds the size limit and decorate it with title + return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { + contentBuilder := strings.Builder{} + WriteContent(&contentBuilder, writer.MarkAsTitle(issuesDetailsSubTitle, 3)) + WriteContent(&contentBuilder, detailsContent) + return contentBuilder.String() + }) } -func getSastViolationFullDescription(issue formats.SourceCodeRow, writer OutputWriter) string { - table := getBaseJasViolationDetailsTable(issue.Watch, issue.Policies, []string{issue.CWE}, writer) - table.AddRow(MarkAsBold("Rule ID:"), issue.RuleId) - return getJasViolationFullDescription(issue, table.Build(), writer) +func getIssuesWithDetails(issues []formats.VulnerabilityOrViolationRow) (filter []formats.VulnerabilityOrViolationRow) { + for i := range issues { + if issues[i].JfrogResearchInformation != nil || issues[i].Summary != "" { + filter = append(filter, issues[i]) + } + } + return } -func SecretViolationReviewContent(issue formats.SourceCodeRow, writer OutputWriter) string { - applicability := "" - if issue.Applicability != nil { - applicability = issue.Applicability.Status +func getComponentIssueIdentifier(key, compName, version, watch string) (id string) { + parts := []string{} + if key != "" { + parts = append(parts, fmt.Sprintf("[ %s ]", key)) + } + parts = append(parts, compName, version) + if watch != "" { + parts = append(parts, fmt.Sprintf("(%s)", watch)) } + return strings.Join(parts, " ") +} + +func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, violations bool, writer OutputWriter) (content string) { var contentBuilder strings.Builder - WriteContent(&contentBuilder, - writer.MarkAsTitle(fmt.Sprintf("%s Violation", secretsTitle), 2), - writer.MarkInCenter(getSecretsDescriptionTable(issue.Severity, issue.IssueId, issue.Finding, applicability, writer)), - writer.MarkAsDetails("Full description", 3, getSecretsViolationFullDescription(issue, writer)), - ) + // Title + WriteContent(&contentBuilder, writer.MarkAsTitle(fmt.Sprintf("%s Details", getIssueType(violations)), 3)) + // Details Table + directComponent := []string{} + for _, component := range issue.ImpactedDependencyDetails.Components { + directComponent = append(directComponent, fmt.Sprintf("%s:%s", component.Name, component.Version)) + } + noHeaderTable := NewMarkdownTable("", "") + if len(issue.Policies) > 0 { + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(issue.Policies...)) + } + if issue.Watch != "" { + noHeaderTable.AddRow(MarkAsBold("Watch Name:"), issue.Watch) + } + if issue.JfrogResearchInformation != nil && issue.JfrogResearchInformation.Severity != "" { + noHeaderTable.AddRow(MarkAsBold("Jfrog Research Severity:"), writer.FormattedSeverity(issue.JfrogResearchInformation.Severity, "Applicable", true)) + } + if issue.Applicable != "" { + noHeaderTable.AddRow(MarkAsBold("Contextual Analysis:"), issue.Applicable) + } + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Direct Dependencies:")), NewCellData(directComponent...)) + noHeaderTable.AddRow(MarkAsBold("Impacted Dependency:"), fmt.Sprintf("%s:%s", issue.ImpactedDependencyName, issue.ImpactedDependencyVersion)) + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Fixed Versions:")), NewCellData(issue.FixedVersions...)) + + cvss := []string{} + for _, cve := range issue.Cves { + cvss = append(cvss, cve.CvssV3) + } + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("CVSS V3:")), NewCellData(cvss...)) + WriteContent(&contentBuilder, noHeaderTable.Build()) + + // Summary + if issue.Summary != "" { + WriteContent(&contentBuilder, issue.Summary) + } + + // Jfrog Research Details + if issue.JfrogResearchInformation == nil { + return contentBuilder.String() + } + WriteContent(&contentBuilder, writer.MarkAsTitle(jfrogResearchDetailsSubTitle, 3)) + + if issue.JfrogResearchInformation.Details != "" { + WriteNewLine(&contentBuilder) + WriteContent(&contentBuilder, MarkAsBold("Description:"), issue.JfrogResearchInformation.Details) + } + if issue.JfrogResearchInformation.Remediation != "" { + WriteNewLine(&contentBuilder) + WriteContent(&contentBuilder, MarkAsBold("Remediation:"), issue.JfrogResearchInformation.Remediation) + } + return contentBuilder.String() } -func getSecretsViolationFullDescription(issue formats.SourceCodeRow, writer OutputWriter) string { - table := getBaseJasViolationDetailsTable(issue.Watch, issue.Policies, []string{issue.CWE}, writer) - table.AddRow(MarkAsBold("Abbreviation:"), issue.RuleId) - return getJasViolationFullDescription(issue, table.Build(), writer) -} +// TODO: DELETE + +// func VulnerabilitiesContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { +// if len(vulnerabilities) == 0 { +// return []string{} +// } +// content = append(content, writer.MarkAsTitle(vulnerableDependenciesTitle, 2)) +// content = append(content, vulnerabilitiesSummaryContent(vulnerabilities, writer)) +// content = append(content, vulnerabilityDetailsContent(vulnerabilities, writer)...) +// return +// } + +// func vulnerabilitiesSummaryContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { +// var contentBuilder strings.Builder +// WriteContent(&contentBuilder, +// writer.MarkAsTitle("✍️ Summary", 3), +// writer.MarkInCenter(getVulnerabilitiesSummaryTable(vulnerabilities, writer)), +// ) +// return contentBuilder.String() +// } + +// type vulnerabilityOrViolationDetails struct { +// details string +// title string +// dependencyName string +// dependencyVersion string +// } + +// func vulnerabilityDetailsContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { +// vulnerabilitiesWithDetails := getVulnerabilityWithDetails(vulnerabilities) +// if len(vulnerabilitiesWithDetails) == 0 { +// return +// } +// // Prepare content for each vulnerability details +// for i := range vulnerabilitiesWithDetails { +// if len(vulnerabilitiesWithDetails) == 1 { +// content = append(content, vulnerabilitiesWithDetails[i].details) +// } else { +// content = append(content, writer.MarkAsDetails( +// fmt.Sprintf(`%s %s %s`, vulnerabilitiesWithDetails[i].title, +// vulnerabilitiesWithDetails[i].dependencyName, +// vulnerabilitiesWithDetails[i].dependencyVersion), +// 4, vulnerabilitiesWithDetails[i].details, +// )) +// } +// } +// // Split content if it exceeds the size limit and decorate it with title +// return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { +// contentBuilder := strings.Builder{} +// WriteContent(&contentBuilder, writer.MarkAsTitle(jfrogResearchDetailsSubTitle, 3)) +// WriteContent(&contentBuilder, detailsContent) +// return contentBuilder.String() +// }) +// } + +// func getVulnerabilityWithDetails(vulnerabilities []formats.VulnerabilityOrViolationRow) (vulnerabilitiesWithDetails []vulnerabilityOrViolationDetails) { +// for i := range vulnerabilities { +// vulDescriptionContent := createVulnerabilityResearchDescription(&vulnerabilities[i]) +// if vulDescriptionContent == "" { +// // No content +// continue +// } +// vulnerabilitiesWithDetails = append(vulnerabilitiesWithDetails, vulnerabilityOrViolationDetails{ +// details: vulDescriptionContent, +// title: getScaCveIdentifier(vulnerabilities[i].Cves, vulnerabilities[i].IssueId), +// dependencyName: vulnerabilities[i].ImpactedDependencyName, +// dependencyVersion: vulnerabilities[i].ImpactedDependencyVersion, +// }) +// } +// return +// } + +// func getScaCveIdentifier(cveRows []formats.CveRow, xrayId string) string { +// identifier := results.GetIssueIdentifier(cveRows, xrayId, ", ") +// if identifier == "" { +// return "" +// } +// return fmt.Sprintf("[ %s ]", identifier) +// } + +// func createVulnerabilityResearchDescription(vulnerability *formats.VulnerabilityOrViolationRow) string { +// var descriptionBuilder strings.Builder +// vulnResearch := vulnerability.JfrogResearchInformation +// if vulnResearch == nil { +// vulnResearch = &formats.JfrogResearchInformation{Details: vulnerability.Summary} +// } else if vulnResearch.Details == "" { +// vulnResearch.Details = vulnerability.Summary +// } + +// if vulnResearch.Details != "" { +// WriteContent(&descriptionBuilder, MarkAsBold("Description:"), vulnResearch.Details) +// } +// if vulnResearch.Remediation != "" { +// if vulnResearch.Details != "" { +// WriteNewLine(&descriptionBuilder) +// } +// WriteContent(&descriptionBuilder, MarkAsBold("Remediation:"), vulnResearch.Remediation) +// } +// return descriptionBuilder.String() +// } diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index b44fa2304..4352eefd4 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -4,10 +4,9 @@ import ( "path/filepath" "testing" + "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/froggit-go/vcsutils" "github.com/jfrog/jfrog-cli-security/utils/formats" - "github.com/jfrog/jfrog-cli-security/utils/jasutils" - "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/stretchr/testify/assert" ) @@ -185,7 +184,7 @@ func TestGetPRSummaryContent(t *testing.T) { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - output := GetPRSummaryContent([]string{MarkAsCodeSnippet("some content")}, tc.issuesExists, tc.isComment, test.writer) + output := GetMainCommentContent([]string{MarkAsCodeSnippet("some content")}, tc.issuesExists, tc.isComment, test.writer) assert.Len(t, output, 1) assert.Equal(t, expectedOutput, output[0]) }) @@ -193,15 +192,17 @@ func TestGetPRSummaryContent(t *testing.T) { } } -func TestVulnerabilitiesContent(t *testing.T) { +func TestScanSummaryContent(t *testing.T) { testCases := []struct { - name string - vulnerabilities []formats.VulnerabilityOrViolationRow - cases []OutputTestCase + name string + violationContext string + includeSecrets bool + issues issues.ScansIssuesCollection + cases []OutputTestCase }{ { - name: "No vulnerabilities", - vulnerabilities: []formats.VulnerabilityOrViolationRow{}, + name: "No issues", + issues: issues.ScansIssuesCollection{}, cases: []OutputTestCase{ { name: "Standard output", @@ -215,283 +216,319 @@ func TestVulnerabilitiesContent(t *testing.T) { }, }, }, - { - name: "One vulnerability", - vulnerabilities: []formats.VulnerabilityOrViolationRow{ - { - Summary: "Summary CVE-2022-26652", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, - ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", - ImpactedDependencyVersion: "v0.21.0", - Components: []formats.ComponentRow{ - { - Name: "github.com/nats-io/nats-streaming-server", - Version: "v0.21.0", - }, - }, - }, - Applicable: "Undetermined", - FixedVersions: []string{"[0.24.3]"}, - JfrogResearchInformation: &formats.JfrogResearchInformation{ - Details: "Research CVE-2022-26652 details", - Remediation: "some remediation", - }, - Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, - }, - }, - cases: []OutputTestCase{ - { - name: "Standard output", - writer: &StandardOutput{}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_standard.md")}, - }, - { - name: "Simplified output", - writer: &SimplifiedOutput{}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_simplified.md")}, - }, - }, - }, - { - name: "One vulnerability, no Details", - vulnerabilities: []formats.VulnerabilityOrViolationRow{ - { - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, - ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", - ImpactedDependencyVersion: "v0.21.0", - Components: []formats.ComponentRow{ - { - Name: "github.com/nats-io/nats-streaming-server", - Version: "v0.21.0", - }, - }, - }, - Applicable: "Undetermined", - FixedVersions: []string{"[0.24.3]"}, - Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, - }, - }, - cases: []OutputTestCase{ - { - name: "Standard output", - writer: &StandardOutput{}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_standard.md")}, - }, - { - name: "Simplified output", - writer: &SimplifiedOutput{}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_simplified.md")}, - }, - }, - }, - { - name: "multiple Vulnerabilities with Contextual Analysis", - vulnerabilities: []formats.VulnerabilityOrViolationRow{ - { - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: severityutils.GetAsDetails(severityutils.Critical, jasutils.NotApplicable, false), - ImpactedDependencyName: "impacted", - ImpactedDependencyVersion: "3.0.0", - Components: []formats.ComponentRow{ - {Name: "dep1", Version: "1.0.0"}, - {Name: "dep2", Version: "2.0.0"}, - }, - }, - Applicable: "Not Applicable", - FixedVersions: []string{"4.0.0", "5.0.0"}, - Cves: []formats.CveRow{{Id: "CVE-1111-11111", Applicability: &formats.Applicability{Status: "Not Applicable"}}}, - }, - { - Summary: "Summary XRAY-122345", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: severityutils.GetAsDetails(severityutils.High, jasutils.ApplicabilityUndetermined, false), - ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", - ImpactedDependencyVersion: "v0.21.0", - Components: []formats.ComponentRow{ - { - Name: "github.com/nats-io/nats-streaming-server", - Version: "v0.21.0", - }, - }, - }, - Applicable: "Undetermined", - FixedVersions: []string{"[0.24.1]"}, - IssueId: "XRAY-122345", - JfrogResearchInformation: &formats.JfrogResearchInformation{ - Remediation: "some remediation", - }, - Cves: []formats.CveRow{{}}, - }, - { - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: severityutils.GetAsDetails(severityutils.Medium, jasutils.Applicable, false), - ImpactedDependencyName: "component-D", - ImpactedDependencyVersion: "v0.21.0", - Components: []formats.ComponentRow{ - { - Name: "component-D", - Version: "v0.21.0", - }, - }, - }, - Applicable: "Applicable", - FixedVersions: []string{"[0.24.3]"}, - JfrogResearchInformation: &formats.JfrogResearchInformation{ - Remediation: "some remediation", - }, - Cves: []formats.CveRow{ - {Id: "CVE-2022-26652"}, - {Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable"}}, - }, - }, - { - Summary: "Summary", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: severityutils.GetAsDetails(severityutils.Low, jasutils.ApplicabilityUndetermined, false), - ImpactedDependencyName: "github.com/mholt/archiver/v3", - ImpactedDependencyVersion: "v3.5.1", - Components: []formats.ComponentRow{ - { - Name: "github.com/mholt/archiver/v3", - Version: "v3.5.1", - }, - }, - }, - Applicable: "Undetermined", - Cves: []formats.CveRow{}, - }, - }, - cases: []OutputTestCase{ - { - name: "Standard output", - writer: &StandardOutput{MarkdownOutput{showCaColumn: true}}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard.md")}, - }, - { - name: "Simplified output", - writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true}}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified.md")}, - }, - { - name: "Split Standard output", - writer: &StandardOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1720, commentSizeLimit: 1720}}, - expectedOutputPath: []string{ - filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split1.md"), - filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split2.md"), - }, - }, - { - name: "Split Simplified output", - writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1000, commentSizeLimit: 1000}}, - expectedOutputPath: []string{ - filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split1.md"), - filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split2.md"), - }, - }, - }, - }, - { - name: "Split vulnerabilities content", - }, } - for _, tc := range testCases { - for _, test := range tc.cases { - t.Run(tc.name+"_"+test.name, func(t *testing.T) { - expectedOutput := GetExpectedTestCaseOutput(t, test) - output := ConvertContentToComments(VulnerabilitiesContent(tc.vulnerabilities, test.writer), test.writer) - assert.Len(t, output, len(expectedOutput)) - assert.ElementsMatch(t, expectedOutput, output) - }) - } - } -} -func TestLicensesContent(t *testing.T) { - testCases := []struct { - name string - licenses []formats.LicenseViolationRow - cases []OutputTestCase - }{ - { - name: "No license violations", - licenses: []formats.LicenseViolationRow{}, - cases: []OutputTestCase{ - { - name: "Standard output", - writer: &StandardOutput{}, - expectedOutput: []string{""}, - }, - { - name: "Simplified output", - writer: &SimplifiedOutput{}, - expectedOutput: []string{""}, - }, - }, - }, - { - name: "License violations", - licenses: []formats.LicenseViolationRow{ - { - LicenseRow: formats.LicenseRow{ - LicenseKey: "License1", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - Components: []formats.ComponentRow{{Name: "Comp1", Version: "1.0"}}, - ImpactedDependencyName: "Dep1", - ImpactedDependencyVersion: "2.0", - SeverityDetails: formats.SeverityDetails{ - Severity: "High", - }, - }, - }, - }, - { - LicenseRow: formats.LicenseRow{ - LicenseKey: "License2", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - Components: []formats.ComponentRow{ - { - Name: "root", - Version: "1.0.0", - }, - { - Name: "minimatch", - Version: "1.2.3", - }, - }, - ImpactedDependencyName: "Dep2", - ImpactedDependencyVersion: "3.0", - SeverityDetails: formats.SeverityDetails{ - Severity: "High", - }, - }, - }, - }, - }, - cases: []OutputTestCase{ - { - name: "Standard output", - writer: &StandardOutput{}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "license", "license_violation_standard.md")}, - }, - { - name: "Simplified output", - writer: &SimplifiedOutput{}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "license", "license_violation_simplified.md")}, - }, - }, - }, - } for _, tc := range testCases { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { - assert.Equal(t, GetExpectedTestOutput(t, test), LicensesContent(tc.licenses, test.writer)) + expectedOutput := GetExpectedTestOutput(t, test) + output := ScanSummaryContent(tc.issues, tc.violationContext, tc.includeSecrets, test.writer) + assert.Len(t, output, 1) + assert.Equal(t, expectedOutput, output[0]) }) } } } +// func TestVulnerabilitiesContent(t *testing.T) { +// testCases := []struct { +// name string +// vulnerabilities []formats.VulnerabilityOrViolationRow +// cases []OutputTestCase +// }{ +// { +// name: "No vulnerabilities", +// vulnerabilities: []formats.VulnerabilityOrViolationRow{}, +// cases: []OutputTestCase{ +// { +// name: "Standard output", +// writer: &StandardOutput{}, +// expectedOutput: []string{""}, +// }, +// { +// name: "Simplified output", +// writer: &SimplifiedOutput{}, +// expectedOutput: []string{""}, +// }, +// }, +// }, +// { +// name: "One vulnerability", +// vulnerabilities: []formats.VulnerabilityOrViolationRow{ +// { +// Summary: "Summary CVE-2022-26652", +// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ +// SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, +// ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", +// ImpactedDependencyVersion: "v0.21.0", +// Components: []formats.ComponentRow{ +// { +// Name: "github.com/nats-io/nats-streaming-server", +// Version: "v0.21.0", +// }, +// }, +// }, +// Applicable: "Undetermined", +// FixedVersions: []string{"[0.24.3]"}, +// JfrogResearchInformation: &formats.JfrogResearchInformation{ +// Details: "Research CVE-2022-26652 details", +// Remediation: "some remediation", +// }, +// Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, +// }, +// }, +// cases: []OutputTestCase{ +// { +// name: "Standard output", +// writer: &StandardOutput{}, +// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_standard.md")}, +// }, +// { +// name: "Simplified output", +// writer: &SimplifiedOutput{}, +// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_simplified.md")}, +// }, +// }, +// }, +// { +// name: "One vulnerability, no Details", +// vulnerabilities: []formats.VulnerabilityOrViolationRow{ +// { +// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ +// SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, +// ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", +// ImpactedDependencyVersion: "v0.21.0", +// Components: []formats.ComponentRow{ +// { +// Name: "github.com/nats-io/nats-streaming-server", +// Version: "v0.21.0", +// }, +// }, +// }, +// Applicable: "Undetermined", +// FixedVersions: []string{"[0.24.3]"}, +// Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, +// }, +// }, +// cases: []OutputTestCase{ +// { +// name: "Standard output", +// writer: &StandardOutput{}, +// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_standard.md")}, +// }, +// { +// name: "Simplified output", +// writer: &SimplifiedOutput{}, +// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_simplified.md")}, +// }, +// }, +// }, +// { +// name: "multiple Vulnerabilities with Contextual Analysis", +// vulnerabilities: []formats.VulnerabilityOrViolationRow{ +// { +// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ +// SeverityDetails: severityutils.GetAsDetails(severityutils.Critical, jasutils.NotApplicable, false), +// ImpactedDependencyName: "impacted", +// ImpactedDependencyVersion: "3.0.0", +// Components: []formats.ComponentRow{ +// {Name: "dep1", Version: "1.0.0"}, +// {Name: "dep2", Version: "2.0.0"}, +// }, +// }, +// Applicable: "Not Applicable", +// FixedVersions: []string{"4.0.0", "5.0.0"}, +// Cves: []formats.CveRow{{Id: "CVE-1111-11111", Applicability: &formats.Applicability{Status: "Not Applicable"}}}, +// }, +// { +// Summary: "Summary XRAY-122345", +// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ +// SeverityDetails: severityutils.GetAsDetails(severityutils.High, jasutils.ApplicabilityUndetermined, false), +// ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", +// ImpactedDependencyVersion: "v0.21.0", +// Components: []formats.ComponentRow{ +// { +// Name: "github.com/nats-io/nats-streaming-server", +// Version: "v0.21.0", +// }, +// }, +// }, +// Applicable: "Undetermined", +// FixedVersions: []string{"[0.24.1]"}, +// IssueId: "XRAY-122345", +// JfrogResearchInformation: &formats.JfrogResearchInformation{ +// Remediation: "some remediation", +// }, +// Cves: []formats.CveRow{{}}, +// }, +// { +// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ +// SeverityDetails: severityutils.GetAsDetails(severityutils.Medium, jasutils.Applicable, false), +// ImpactedDependencyName: "component-D", +// ImpactedDependencyVersion: "v0.21.0", +// Components: []formats.ComponentRow{ +// { +// Name: "component-D", +// Version: "v0.21.0", +// }, +// }, +// }, +// Applicable: "Applicable", +// FixedVersions: []string{"[0.24.3]"}, +// JfrogResearchInformation: &formats.JfrogResearchInformation{ +// Remediation: "some remediation", +// }, +// Cves: []formats.CveRow{ +// {Id: "CVE-2022-26652"}, +// {Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable"}}, +// }, +// }, +// { +// Summary: "Summary", +// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ +// SeverityDetails: severityutils.GetAsDetails(severityutils.Low, jasutils.ApplicabilityUndetermined, false), +// ImpactedDependencyName: "github.com/mholt/archiver/v3", +// ImpactedDependencyVersion: "v3.5.1", +// Components: []formats.ComponentRow{ +// { +// Name: "github.com/mholt/archiver/v3", +// Version: "v3.5.1", +// }, +// }, +// }, +// Applicable: "Undetermined", +// Cves: []formats.CveRow{}, +// }, +// }, +// cases: []OutputTestCase{ +// { +// name: "Standard output", +// writer: &StandardOutput{MarkdownOutput{showCaColumn: true}}, +// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard.md")}, +// }, +// { +// name: "Simplified output", +// writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true}}, +// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified.md")}, +// }, +// { +// name: "Split Standard output", +// writer: &StandardOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1720, commentSizeLimit: 1720}}, +// expectedOutputPath: []string{ +// filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split1.md"), +// filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split2.md"), +// }, +// }, +// { +// name: "Split Simplified output", +// writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1000, commentSizeLimit: 1000}}, +// expectedOutputPath: []string{ +// filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split1.md"), +// filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split2.md"), +// }, +// }, +// }, +// }, +// { +// name: "Split vulnerabilities content", +// }, +// } +// for _, tc := range testCases { +// for _, test := range tc.cases { +// t.Run(tc.name+"_"+test.name, func(t *testing.T) { +// expectedOutput := GetExpectedTestCaseOutput(t, test) +// output := ConvertContentToComments(VulnerabilitiesContent(tc.vulnerabilities, test.writer), test.writer) +// assert.Len(t, output, len(expectedOutput)) +// assert.ElementsMatch(t, expectedOutput, output) +// }) +// } +// } +// } + +// func TestLicensesContent(t *testing.T) { +// testCases := []struct { +// name string +// licenses []formats.LicenseViolationRow +// cases []OutputTestCase +// }{ +// { +// name: "No license violations", +// licenses: []formats.LicenseViolationRow{}, +// cases: []OutputTestCase{ +// { +// name: "Standard output", +// writer: &StandardOutput{}, +// expectedOutput: []string{""}, +// }, +// { +// name: "Simplified output", +// writer: &SimplifiedOutput{}, +// expectedOutput: []string{""}, +// }, +// }, +// }, +// { +// name: "License violations", +// licenses: []formats.LicenseViolationRow{ +// { +// LicenseRow: formats.LicenseRow{ +// LicenseKey: "License1", +// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ +// Components: []formats.ComponentRow{{Name: "Comp1", Version: "1.0"}}, +// ImpactedDependencyName: "Dep1", +// ImpactedDependencyVersion: "2.0", +// SeverityDetails: formats.SeverityDetails{ +// Severity: "High", +// }, +// }, +// }, +// }, +// { +// LicenseRow: formats.LicenseRow{ +// LicenseKey: "License2", +// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ +// Components: []formats.ComponentRow{ +// { +// Name: "root", +// Version: "1.0.0", +// }, +// { +// Name: "minimatch", +// Version: "1.2.3", +// }, +// }, +// ImpactedDependencyName: "Dep2", +// ImpactedDependencyVersion: "3.0", +// SeverityDetails: formats.SeverityDetails{ +// Severity: "High", +// }, +// }, +// }, +// }, +// }, +// cases: []OutputTestCase{ +// { +// name: "Standard output", +// writer: &StandardOutput{}, +// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "license", "license_violation_standard.md")}, +// }, +// { +// name: "Simplified output", +// writer: &SimplifiedOutput{}, +// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "license", "license_violation_simplified.md")}, +// }, +// }, +// }, +// } +// for _, tc := range testCases { +// for _, test := range tc.cases { +// t.Run(tc.name+"_"+test.name, func(t *testing.T) { +// assert.Equal(t, GetExpectedTestOutput(t, test), LicensesContent(tc.licenses, test.writer)) +// }) +// } +// } +// } + func TestIsFrogbotReviewComment(t *testing.T) { testCases := []struct { name string @@ -641,15 +678,19 @@ func TestApplicableReviewContent(t *testing.T) { func TestSecretsReviewContent(t *testing.T) { testCases := []struct { - name string - severity, finding, fullDetails, status string - cases []OutputTestCase + name string + issue formats.SourceCodeRow + violations bool + cases []OutputTestCase }{ { - name: "Secret review comment content", - severity: "Medium", - finding: "Secret keys were found", - fullDetails: "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", + name: "Secret review comment content", + issue: formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + IssueId: "secret-scanner-id", + Finding: "Secret keys were found", + ScannerDescription: "Scanner Description....", + }, cases: []OutputTestCase{ { name: "Standard output", @@ -664,11 +705,14 @@ func TestSecretsReviewContent(t *testing.T) { }, }, { - name: "Secret review comment content with applicability status", - severity: "Medium", - status: "Active", - finding: "Secret keys were found", - fullDetails: "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", + name: "Secret review comment content with applicability status", + issue: formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Applicability: &formats.Applicability{Status: "Active"}, + IssueId: "secret-scanner-id", + Finding: "Secret keys were found", + ScannerDescription: "Scanner Description....", + }, cases: []OutputTestCase{ { name: "Standard output", @@ -682,13 +726,40 @@ func TestSecretsReviewContent(t *testing.T) { }, }, }, + { + name: "Secret violation review comment content with applicability status", + violations: true, + issue: formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Applicability: &formats.Applicability{Status: "Active"}, + IssueId: "secret-scanner-id", + Finding: "Secret keys were found", + ScannerDescription: "Scanner Description....", + ViolationContext: formats.ViolationContext{ + Watch: "jas-watch", + Policies: []string{"policy1"}, + }, + }, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_violation_review_content_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_violation_review_content_simplified.md")}, + }, + }, + }, } for _, tc := range testCases { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - assert.Equal(t, expectedOutput, SecretReviewContent(tc.severity, "id", tc.finding, tc.fullDetails, tc.status, test.writer)) + assert.Equal(t, expectedOutput, SecretReviewContent(tc.issue, tc.violations, test.writer)) }) } } @@ -696,15 +767,18 @@ func TestSecretsReviewContent(t *testing.T) { func TestIacReviewContent(t *testing.T) { testCases := []struct { - name string - severity, finding, fullDetails string - cases []OutputTestCase + name string + violations bool + issue formats.SourceCodeRow + cases []OutputTestCase }{ { - name: "Iac review comment content", - severity: "Medium", - finding: "Missing auto upgrade was detected", - fullDetails: "Resource `google_container_node_pool` should have `management.auto_upgrade=true`\n\nVulnerable example - \n```\nresource \"google_container_node_pool\" \"vulnerable_example\" {\n management {\n auto_upgrade = false\n }\n}\n```\n", + name: "Iac review comment content", + issue: formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Finding: "Missing auto upgrade was detected", + ScannerDescription: "Scanner Description....", + }, cases: []OutputTestCase{ { name: "Standard output", @@ -718,13 +792,39 @@ func TestIacReviewContent(t *testing.T) { }, }, }, + { + name: "Iac violation review comment content", + violations: true, + issue: formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + IssueId: "iac-scanner-id", + Finding: "Missing auto upgrade was detected", + ScannerDescription: "Scanner Description....", + ViolationContext: formats.ViolationContext{ + Watch: "jas-watch", + Policies: []string{"policy1", "policy2"}, + }, + }, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "iac", "iac_violation_review_content_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "iac", "iac_violation_review_content_simplified.md")}, + }, + }, + }, } for _, tc := range testCases { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - assert.Equal(t, expectedOutput, IacReviewContent(tc.severity, tc.finding, tc.fullDetails, test.writer)) + assert.Equal(t, expectedOutput, IacReviewContent(tc.issue, tc.violations, test.writer)) }) } } @@ -732,53 +832,73 @@ func TestIacReviewContent(t *testing.T) { func TestSastReviewContent(t *testing.T) { testCases := []struct { - name string - severity string - finding string - fullDetails string - codeFlows [][]formats.Location - cases []OutputTestCase + name string + violations bool + issue formats.SourceCodeRow + cases []OutputTestCase }{ { - name: "Sast review comment content", - severity: "Low", - finding: "Stack Trace Exposure", - fullDetails: "\n### Overview\nStack trace exposure is a type of security vulnerability that occurs when a program reveals\nsensitive information, such as the names and locations of internal files and variables,\nin error messages or other diagnostic output. This can happen when a program crashes or\nencounters an error, and the stack trace (a record of the program's call stack at the time\nof the error) is included in the output.", - codeFlows: [][]formats.Location{ + name: "No code flows", + issue: formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + Finding: "Stack Trace Exposure", + ScannerDescription: "Scanner Description....", + }, + cases: []OutputTestCase{ { - { - File: "file2", - StartLine: 1, - StartColumn: 2, - EndLine: 3, - EndColumn: 4, - Snippet: "other-snippet", - }, - { - File: "file", - StartLine: 0, - StartColumn: 0, - EndLine: 0, - EndColumn: 0, - Snippet: "snippet", - }, + name: "Standard output", + writer: &StandardOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_review_content_no_code_flow_standard.md")}, }, { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_review_content_no_code_flow_simplified.md")}, + }, + }, + }, + { + name: "Sast review comment content", + issue: formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + Finding: "Stack Trace Exposure", + ScannerDescription: "Scanner Description....", + CodeFlow: [][]formats.Location{ { - File: "file", - StartLine: 10, - StartColumn: 20, - EndLine: 10, - EndColumn: 30, - Snippet: "a-snippet", + { + File: "file2", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "other-snippet", + }, + { + File: "file", + StartLine: 0, + StartColumn: 0, + EndLine: 0, + EndColumn: 0, + Snippet: "snippet", + }, }, { - File: "file", - StartLine: 0, - StartColumn: 0, - EndLine: 0, - EndColumn: 0, - Snippet: "snippet", + { + File: "file", + StartLine: 10, + StartColumn: 20, + EndLine: 10, + EndColumn: 30, + Snippet: "a-snippet", + }, + { + File: "file", + StartLine: 0, + StartColumn: 0, + EndLine: 0, + EndColumn: 0, + Snippet: "snippet", + }, }, }, }, @@ -796,20 +916,65 @@ func TestSastReviewContent(t *testing.T) { }, }, { - name: "No code flows", - severity: "Low", - finding: "Stack Trace Exposure", - fullDetails: "\n### Overview\nStack trace exposure is a type of security vulnerability that occurs when a program reveals\nsensitive information, such as the names and locations of internal files and variables,\nin error messages or other diagnostic output. This can happen when a program crashes or\nencounters an error, and the stack trace (a record of the program's call stack at the time\nof the error) is included in the output.", + name: "Sast violation review comment content", + violations: true, + issue: formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + Finding: "Stack Trace Exposure", + ScannerDescription: "Scanner Description....", + ViolationContext: formats.ViolationContext{ + Watch: "jas-watch", + Policies: []string{"policy1", "policy2"}, + }, + CodeFlow: [][]formats.Location{ + { + { + File: "file2", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "other-snippet", + }, + { + File: "file", + StartLine: 0, + StartColumn: 0, + EndLine: 0, + EndColumn: 0, + Snippet: "snippet", + }, + }, + { + { + File: "file", + StartLine: 10, + StartColumn: 20, + EndLine: 10, + EndColumn: 30, + Snippet: "a-snippet", + }, + { + File: "file", + StartLine: 0, + StartColumn: 0, + EndLine: 0, + EndColumn: 0, + Snippet: "snippet", + }, + }, + }, + }, cases: []OutputTestCase{ { name: "Standard output", writer: &StandardOutput{}, - expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_review_content_no_code_flow_standard.md")}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_violation_review_content_standard.md")}, }, { name: "Simplified output", writer: &SimplifiedOutput{}, - expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_review_content_no_code_flow_simplified.md")}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_violation_review_content_simplified.md")}, }, }, }, @@ -819,7 +984,7 @@ func TestSastReviewContent(t *testing.T) { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - assert.Equal(t, expectedOutput, SastReviewContent(tc.severity, tc.finding, tc.fullDetails, tc.codeFlows, test.writer)) + assert.Equal(t, expectedOutput, SastReviewContent(tc.issue, tc.violations, test.writer)) }) } } diff --git a/utils/outputwriter/outputwriter.go b/utils/outputwriter/outputwriter.go index b50e677b4..dea7b6e1d 100644 --- a/utils/outputwriter/outputwriter.go +++ b/utils/outputwriter/outputwriter.go @@ -105,7 +105,7 @@ type OutputWriter interface { VcsProvider() vcsutils.VcsProvider SetVcsProvider(provider vcsutils.VcsProvider) // Markdown interface - FormattedSeverity(severity, applicability string) string + FormattedSeverity(severity, applicability string, small bool) string Separator() string MarkInCenter(content string) string MarkAsDetails(summary string, subTitleDepth int, content string) string diff --git a/utils/outputwriter/simplifiedoutput.go b/utils/outputwriter/simplifiedoutput.go index 114e19630..30d437cb3 100644 --- a/utils/outputwriter/simplifiedoutput.go +++ b/utils/outputwriter/simplifiedoutput.go @@ -17,7 +17,7 @@ func (smo *SimplifiedOutput) Separator() string { return simpleSeparator } -func (smo *SimplifiedOutput) FormattedSeverity(severity, _ string) string { +func (smo *SimplifiedOutput) FormattedSeverity(severity, _ string, _ bool) string { return severity } diff --git a/utils/outputwriter/simplifiedoutput_test.go b/utils/outputwriter/simplifiedoutput_test.go index 6dc860cfb..82ac0896e 100644 --- a/utils/outputwriter/simplifiedoutput_test.go +++ b/utils/outputwriter/simplifiedoutput_test.go @@ -57,7 +57,7 @@ func TestSimpleFormattedSeverity(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { smo := &SimplifiedOutput{} - assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability)) + assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability, false)) }) } } diff --git a/utils/outputwriter/standardoutput.go b/utils/outputwriter/standardoutput.go index 2a1a50aa3..28e5df141 100644 --- a/utils/outputwriter/standardoutput.go +++ b/utils/outputwriter/standardoutput.go @@ -13,8 +13,12 @@ func (so *StandardOutput) Separator() string { return "
" } -func (so *StandardOutput) FormattedSeverity(severity, applicability string) string { - return fmt.Sprintf("%s%8s", getSeverityTag(IconName(severity), applicability), severity) +func (so *StandardOutput) FormattedSeverity(severity, applicability string, small bool) string { + if small { + return fmt.Sprintf("%s%8s", getSmallSeverityTag(IconName(severity)), severity) + } else { + return fmt.Sprintf("%s%8s", getSeverityTag(IconName(severity), applicability), severity) + } } func (so *StandardOutput) Image(source ImageSource) string { diff --git a/utils/outputwriter/standardoutput_test.go b/utils/outputwriter/standardoutput_test.go index 133c01905..b2adf244e 100644 --- a/utils/outputwriter/standardoutput_test.go +++ b/utils/outputwriter/standardoutput_test.go @@ -73,7 +73,7 @@ func TestStandardFormattedSeverity(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { smo := &StandardOutput{} - assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability)) + assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability, false)) }) } } diff --git a/utils/params.go b/utils/params.go index de1ed7bcd..0bf548687 100644 --- a/utils/params.go +++ b/utils/params.go @@ -277,6 +277,7 @@ func (s *Scan) setDefaultsIfNeeded() (err error) { type JFrogPlatform struct { XrayVersion string XscVersion string + ViolationContext ViolationContext `yaml:"violationContext,omitempty"` Watches []string `yaml:"watches,omitempty"` JFrogProjectKey string `yaml:"jfrogProjectKey,omitempty"` } @@ -288,7 +289,6 @@ func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { return } } - if jp.JFrogProjectKey == "" { if err = readParamFromEnv(jfrogProjectEnv, &jp.JFrogProjectKey); err != nil && !e.IsMissingEnvErr(err) { return @@ -296,6 +296,21 @@ func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { // We don't want to return an error from this function if the error is of type ErrMissingEnv because JFrogPlatform environment variables are not mandatory. err = nil } + if jp.ViolationContext == None { + var violationContextStr string + if err = readParamFromEnv(ViolationContextEnv, &violationContextStr); err != nil && !e.IsMissingEnvErr(err) { + return + } + jp.ViolationContext = ViolationContext(violationContextStr) + } + // Validate or set Default base on other params + if jp.ViolationContext == None { + if len(jp.Watches) > 0 { + jp.ViolationContext = WatchContext + } else if jp.JFrogProjectKey != "" { + jp.ViolationContext = ProjectContext + } + } return } diff --git a/utils/utils.go b/utils/utils.go index 54a006c0a..3fb99e1b7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -50,6 +50,15 @@ const ( JfrogHomeDirEnv = "JFROG_CLI_HOME_DIR" ) +// ViolationContext is a type for violation context (None,Project,GitRepo) +const( + None ViolationContext = "" // No violation context + WatchContext ViolationContext = "watch violations" + ProjectContext ViolationContext = "project violations" + GitRepoContext ViolationContext = "Git repository violations" +) +type ViolationContext string + var ( TrueVal = true FrogbotVersion = "0.0.0" From 483615457bd268e6fa30bbb1d8b4069ee80f6003 Mon Sep 17 00:00:00 2001 From: attiasas Date: Thu, 5 Dec 2024 11:54:40 +0200 Subject: [PATCH 13/34] merge fix --- utils/outputwriter/outputcontent.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index b128dfc0b..9007f1ba0 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -571,10 +571,8 @@ func SecretReviewContent(issue formats.SourceCodeRow, violation bool, writer Out func getSecretsDescriptionTable(severity, issueId, finding, status string, writer OutputWriter) string { // Determine the issue applicable status applicability := jasutils.Applicable.String() - if status != "" { - if status == jasutils.Inactive.String() { - applicability = jasutils.NotApplicable.String() - } + if status == jasutils.Inactive.String() { + applicability = jasutils.NotApplicable.String() } columns := []string{"Severity"} rowData := []string{writer.FormattedSeverity(severity, applicability, false)} From 77b33ce82f4f2bddbcc2e80b78f370f58cd9cf1e Mon Sep 17 00:00:00 2001 From: attiasas Date: Sat, 7 Dec 2024 18:15:11 +0200 Subject: [PATCH 14/34] done implementation --- go.mod | 5 +- go.sum | 2 - scanpullrequest/scanpullrequest.go | 72 +++++++++++- scanrepository/scanrepository.go | 8 +- utils/comment.go | 79 ++++++++----- utils/consts.go | 2 +- utils/issues/issuescollection.go | 123 +++++++++++++++---- utils/issues/issuescollection_test.go | 13 +- utils/outputwriter/icons.go | 53 +++++---- utils/outputwriter/markdowntable.go | 54 ++++++--- utils/outputwriter/outputcontent.go | 137 +++++++++++++++------- utils/outputwriter/outputcontent_test.go | 16 ++- utils/outputwriter/outputwriter.go | 4 +- utils/outputwriter/simplifiedoutput.go | 8 +- utils/outputwriter/standardoutput.go | 26 ++-- utils/outputwriter/standardoutput_test.go | 2 +- utils/params.go | 19 ++- utils/scandetails.go | 3 +- utils/utils.go | 11 +- 19 files changed, 449 insertions(+), 188 deletions(-) diff --git a/go.mod b/go.mod index 810858ea1..34a74fdcd 100644 --- a/go.mod +++ b/go.mod @@ -121,8 +121,9 @@ require ( ) // eranturgeman:jas-violations-support -// replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241204143029-e901cd468c75 +replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security + +// replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241204143029-e901cd468c75 // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc diff --git a/go.sum b/go.sum index 05e61e85a..60660ac4b 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,6 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241204143029-e901cd468c75 h1:iUJrYLb6FWxcvLsrN8o8QEpULwcytMCJnzt3suCvqZo= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241204143029-e901cd468c75/go.mod h1:c4GycFVqXqYOjI/1FdJ1lUf//kqQBGEzds1iSn73OaM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 692af4770..d084d9f92 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -41,7 +41,7 @@ func (cmd *ScanPullRequestCmd) Run(configAggregator utils.RepoAggregator, client return } } - repoConfig.OutputWriter.SetHasInternetConnection(frogbotRepoConnection.IsConnected()) + repoConfig.OutputWriter.SetHasInternetConnection( /*false*/ frogbotRepoConnection.IsConnected()) if repoConfig.PullRequestDetails, err = client.GetPullRequestByID(context.Background(), repoConfig.RepoOwner, repoConfig.RepoName, int(repoConfig.PullRequestDetails.ID)); err != nil { return } @@ -95,13 +95,13 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er // Audit PR code issues, err := auditPullRequest(repo, client) if err != nil { - return + return errors.Join(err, utils.HandlePullRequestErrorComment(issues, repo, client, int(pullRequestDetails.ID), err)) } // Output results shouldSendExposedSecretsEmail := issues.SecretsIssuesExists() && repo.SmtpServer != "" if shouldSendExposedSecretsEmail { - secretsEmailDetails := utils.NewSecretsEmailDetails(client, repo, issues.GetUniqueSecretsIssues()) + secretsEmailDetails := utils.NewSecretsEmailDetails(client, repo, append(issues.SecretsVulnerabilities, issues.SecretsViolations...)) if err = utils.AlertSecretsExposed(secretsEmailDetails); err != nil { return } @@ -134,14 +134,20 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) } repoConfig.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP + gitRepoContextValue := "" + if repoConfig.ViolationContext == utils.GitRepoContext { + // The violation context is the Git repository, inject the Git repository context to the scan details + gitRepoContextValue = repositoryInfo.CloneInfo.HTTP + } + scanDetails := utils.NewScanDetails(client, &repoConfig.Server, &repoConfig.Git). - SetXrayGraphScanParams(repositoryInfo.CloneInfo.HTTP, repoConfig.Watches, repoConfig.JFrogProjectKey, len(repoConfig.AllowedLicenses) > 0). + SetXrayGraphScanParams(gitRepoContextValue, repoConfig.Watches, repoConfig.JFrogProjectKey, len(repoConfig.AllowedLicenses) > 0). SetFixableOnly(repoConfig.FixableOnly). SetFailOnInstallationErrors(*repoConfig.FailOnSecurityIssues). SetConfigProfile(repoConfig.ConfigProfile). SetSkipAutoInstall(repoConfig.SkipAutoInstall). - SetXscGitInfoContext(repoConfig.PullRequestDetails.Source.Name, repoConfig.Project, client). SetDisableJas(repoConfig.DisableJas) + if scanDetails, err = scanDetails.SetMinSeverity(repoConfig.MinSeverity); err != nil { return } @@ -167,6 +173,8 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) scanDetails.SetProject(&repoConfig.Projects[i]) var projectIssues *issues.ScansIssuesCollection if projectIssues, err = auditPullRequestInProject(repoConfig, scanDetails); err != nil { + // Make sure status on scans are passed to show in the summary + issuesCollection.AppendStatus(projectIssues.ScanStatus) return } issuesCollection.Append(projectIssues) @@ -191,6 +199,8 @@ func auditPullRequestInProject(repoConfig *utils.Repository, scanDetails *utils. log.Info("Scanning source branch...") sourceResults = scanDetails.RunInstallAndAudit(workingDirs...) if err = sourceResults.GetErrors(); err != nil { + // We get the scan status even if the scan failed to report the scan status in the summary + auditIssues = getResultScanStatues(scanDetails.IncludeVulnerabilities, scanDetails.HasViolationContext(), sourceResults) return } @@ -231,6 +241,8 @@ func auditTargetBranch(repoConfig *utils.Repository, scanDetails *utils.ScanDeta log.Info("Scanning target branch...") targetResults = scanDetails.RunInstallAndAudit(workingDirs...) if err = targetResults.GetErrors(); err != nil { + // We get the scan status even if the scan failed to report the scan status in the summary + newIssues = getResultScanStatues(scanDetails.IncludeVulnerabilities, scanDetails.HasViolationContext(), sourceScanResults, targetResults) return } @@ -310,6 +322,7 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] return nil, err } return &issues.ScansIssuesCollection{ + ScanStatus: simpleJsonResults.Statuses, ScaVulnerabilities: simpleJsonResults.Vulnerabilities, ScaViolations: simpleJsonResults.SecurityViolations, LicensesViolations: simpleJsonResults.LicensesViolations, @@ -325,6 +338,19 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] }, nil } +func getResultScanStatues(includeVulnerabilities, hasViolationContext bool, cmdResults ...*results.SecurityCommandResults) *issues.ScansIssuesCollection { + convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext, SimplifiedOutput: true}) + converted := make([]formats.SimpleJsonResults, len(cmdResults)) + for i, cmdResult := range cmdResults { + var err error + if converted[i], err = convertor.ConvertToSimpleJson(cmdResult); err != nil { + log.Debug(fmt.Sprintf("Failed to get scan status for failed scan #%d. Error: %s", i, err.Error())) + continue + } + } + return &issues.ScansIssuesCollection{ScanStatus: getScanStatus(converted...)} +} + // Returns all the issues found in the source branch that didn't exist in the target branch. func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*issues.ScansIssuesCollection, error) { var err error @@ -338,6 +364,7 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe return nil, err } return &issues.ScansIssuesCollection{ + ScanStatus: getScanStatus(simpleJsonTarget, simpleJsonSource), ScaVulnerabilities: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities), ScaViolations: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.SecurityViolations, simpleJsonSource.SecurityViolations), IacVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities), @@ -350,6 +377,41 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe }, nil } +func getScanStatus(cmdResults ...formats.SimpleJsonResults) formats.ScanStatus { + if len(cmdResults) == 0 { + return formats.ScanStatus{} + } + if len(cmdResults) == 1 { + return cmdResults[0].Statuses + } + statuses := cmdResults[0].Statuses + for _, sourceResults := range cmdResults[1:] { + statuses.ScaStatusCode = getWorstScanStatus(statuses.ScaStatusCode, sourceResults.Statuses.ScaStatusCode) + statuses.IacStatusCode = getWorstScanStatus(statuses.IacStatusCode, sourceResults.Statuses.IacStatusCode) + statuses.SecretsStatusCode = getWorstScanStatus(statuses.SecretsStatusCode, sourceResults.Statuses.SecretsStatusCode) + statuses.SastStatusCode = getWorstScanStatus(statuses.SastStatusCode, sourceResults.Statuses.SastStatusCode) + statuses.ApplicabilityStatusCode = getWorstScanStatus(statuses.ApplicabilityStatusCode, sourceResults.Statuses.ApplicabilityStatusCode) + } + return statuses +} + +func getWorstScanStatus(targetStatus, sourceStatus *int) *int { + if sourceStatus == nil && targetStatus == nil { + // Scan not performed. + return nil + } + if targetStatus == nil { + return sourceStatus + } + if sourceStatus == nil { + return targetStatus + } + if *sourceStatus == 0 { + return targetStatus + } + return sourceStatus +} + func createNewSourceCodeRows(targetResults, sourceResults []formats.SourceCodeRow) []formats.SourceCodeRow { targetSourceCodeVulnerabilitiesKeys := datastructures.MakeSet[string]() for _, row := range targetResults { diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index 7297c731f..076e14989 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -127,9 +127,15 @@ func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Reposito return } + gitRepoContextValue := "" + if repository.ViolationContext == utils.GitRepoContext { + // The violation context is the Git repository, inject the Git repository context to the scan details + gitRepoContextValue = repositoryInfo.CloneInfo.HTTP + } + // Set the scan details cfp.scanDetails = utils.NewScanDetails(client, &repository.Server, &repository.Git). - SetXrayGraphScanParams(repositoryInfo.CloneInfo.HTTP, repository.Watches, repository.JFrogProjectKey, len(repository.AllowedLicenses) > 0). + SetXrayGraphScanParams(gitRepoContextValue, repository.Watches, repository.JFrogProjectKey, len(repository.AllowedLicenses) > 0). SetFailOnInstallationErrors(*repository.FailOnSecurityIssues). SetFixableOnly(repository.FixableOnly). SetSkipAutoInstall(repository.SkipAutoInstall). diff --git a/utils/comment.go b/utils/comment.go index f2b935e7c..182494641 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -32,6 +32,20 @@ const ( commentRemovalErrorMsg = "An error occurred while attempting to remove older Frogbot pull request comments:" ) +func HandlePullRequestErrorComment(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int, scanError error) (err error) { + if issues == nil { + log.Debug("Can't generate error comment without issues collection") + return + } + writer := repo.OutputWriter + for _, comment := range outputwriter.GetFrogbotErrorCommentContent([]string{outputwriter.ScanSummaryContent(*issues, getViolationContextText(repo.ViolationContext), repo.PullRequestSecretComments, writer)}, scanError, writer) { + if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, comment, pullRequestID); err != nil { + return errors.New("couldn't add pull request comment: " + err.Error()) + } + } + return +} + func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int) (err error) { if !repo.Params.AvoidPreviousPrCommentsDeletion { // The removal of comments may fail for various reasons, @@ -107,18 +121,31 @@ func GenerateFixPullRequestDetails(vulnerabilities []formats.VulnerabilityOrViol return } +func getViolationContextText(violationContext ViolationContext) string { + switch violationContext { + case WatchContext: + return outputwriter.WatchViolations + case ProjectContext: + return outputwriter.ProjectViolations + case GitRepoContext: + return outputwriter.GitRepoViolations + default: + return outputwriter.NoViolations + } +} + func generatePullRequestSummaryComment(issuesCollection *issues.ScansIssuesCollection, violationContext ViolationContext, includeSecrets bool, writer outputwriter.OutputWriter) []string { if !issuesCollection.IssuesExists(includeSecrets) { // No Issues return outputwriter.GetMainCommentContent([]string{}, false, true, writer) } - content := []string{} - // if violationContext != None { - content = append(content, outputwriter.ScanSummaryContent(*issuesCollection, string(violationContext), includeSecrets, writer)) - // } + // Summary + content := []string{outputwriter.ScanSummaryContent(*issuesCollection, getViolationContextText(violationContext), includeSecrets, writer)} + // Violations if violationsContent := outputwriter.PolicyViolationsContent(*issuesCollection, writer); len(violationsContent) > 0 { content = append(content, violationsContent...) } + // Vulnerabilities if vulnerabilitiesContent := outputwriter.GetVulnerabilitiesContent(issuesCollection.ScaVulnerabilities, writer); len(vulnerabilitiesContent) > 0 { content = append(content, vulnerabilitiesContent...) } @@ -190,26 +217,33 @@ func getFrogbotComments(writer outputwriter.OutputWriter, existingComments []vcs func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection) (commentsToAdd []ReviewComment) { writer := repo.OutputWriter - for _, applicableEvidence := range issues.GetApplicableEvidences() { - commentsToAdd = append(commentsToAdd, generateReviewComment(ApplicableComment, applicableEvidence.Location, generateApplicabilityReviewContent(applicableEvidence, cve, vulnerability, writer))) + // CVE Applicable Evidence review comments + for _, applicableEvidences := range issues.GetApplicableEvidences() { + commentsToAdd = append(commentsToAdd, generateReviewComment(ApplicableComment, applicableEvidences.Evidence.Location, generateApplicabilityReviewContent(applicableEvidences, writer))) } + // IAC review comments for _, iac := range issues.IacVulnerabilities { commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, iac.Location, generateSourceCodeVulnerabilityReviewContent(IacComment, iac, writer))) } for _, iac := range issues.IacViolations { - + commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, iac.Location, generateSourceCodeViolationReviewContent(IacComment, iac, writer))) } - for _, sast := range issues.GetUniqueSastIssues() { + // SAST review comments + for _, sast := range issues.SastVulnerabilities { commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeVulnerabilityReviewContent(SastComment, sast, writer))) } + for _, sast := range issues.SastViolations { + commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeViolationReviewContent(SastComment, sast, writer))) + } + // Secrets review comments if !repo.Params.PullRequestSecretComments { return } - for _, secret := range issues.GetUniqueSecretsIssues() { + for _, secret := range issues.SecretsVulnerabilities { commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeVulnerabilityReviewContent(SecretComment, secret, writer))) } - for _, secret := range issues.Secrets { - commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeReviewContent(SecretComment, secret, writer))) + for _, secret := range issues.SecretsViolations { + commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeViolationReviewContent(SecretComment, secret, writer))) } return } @@ -228,31 +262,18 @@ func generateReviewComment(commentType ReviewCommentType, location formats.Locat } -func generateApplicabilityReviewContent(issue formats.Evidence, relatedCve formats.CveRow, relatedVulnerability formats.VulnerabilityOrViolationRow, writer outputwriter.OutputWriter) string { - remediation := "" - if relatedVulnerability.JfrogResearchInformation != nil { - remediation = relatedVulnerability.JfrogResearchInformation.Remediation - } - return outputwriter.GenerateReviewCommentContent(outputwriter.ApplicableCveReviewContent( - relatedVulnerability.Severity, - issue.Reason, - relatedCve.Applicability.ScannerDescription, - relatedCve.Id, - relatedVulnerability.Summary, - fmt.Sprintf("%s:%s", relatedVulnerability.ImpactedDependencyName, relatedVulnerability.ImpactedDependencyVersion), - remediation, - writer, - ), writer) +func generateApplicabilityReviewContent(issue issues.ApplicableEvidences, writer outputwriter.OutputWriter) string { + return outputwriter.GenerateReviewCommentContent(outputwriter.ApplicableCveReviewContent(issue, writer), writer) } func generateSourceCodeVulnerabilityReviewContent(commentType ReviewCommentType, issue formats.SourceCodeRow, writer outputwriter.OutputWriter) (content string) { switch commentType { case IacComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(issue, false ,writer), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(issue, false, writer), writer) case SastComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(issue, false ,writer), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(issue, false, writer), writer) case SecretComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(issue, false ,writer), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(issue, false, writer), writer) } return } diff --git a/utils/consts.go b/utils/consts.go index 658dbccef..3cf30cb86 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -57,7 +57,7 @@ const ( PathExclusionsEnv = "JF_PATH_EXCLUSIONS" jfrogWatchesEnv = "JF_WATCHES" jfrogProjectEnv = "JF_PROJECT" - ViolationContextEnv = "JF_VIOLATION_CONTEXT" + ViolationContextEnv = "JF_VIOLATION_CONTEXT" IncludeAllVulnerabilitiesEnv = "JF_INCLUDE_ALL_VULNERABILITIES" AvoidPreviousPrCommentsDeletionEnv = "JF_AVOID_PREVIOUS_PR_COMMENTS_DELETION" FailOnSecurityIssuesEnv = "JF_FAIL" diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index 71c834196..b8d02bd8a 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -2,12 +2,16 @@ package issues import ( // "github.com/jfrog/gofrog/datastructures" + "fmt" + "maps" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" ) - // TODO: after refactor, move this to security-cli as a new formats or remove this and use the existing formats // Group issues by scan type type ScansIssuesCollection struct { @@ -35,7 +39,7 @@ func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { return } // Status - ic.appendStatus(issues.ScanStatus) + ic.AppendStatus(issues.ScanStatus) // Sca if len(issues.ScaVulnerabilities) > 0 { ic.ScaVulnerabilities = append(ic.ScaVulnerabilities, issues.ScaVulnerabilities...) @@ -69,20 +73,20 @@ func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { } } -func (ic ScansIssuesCollection) appendStatus(scanStatus formats.ScanStatus) { - if ic.ScaStatusCode == nil || *ic.ScaStatusCode == 0 { +func (ic *ScansIssuesCollection) AppendStatus(scanStatus formats.ScanStatus) { + if ic.ScaStatusCode == nil || (*ic.ScaStatusCode == 0 && scanStatus.ScaStatusCode != nil) { ic.ScaStatusCode = scanStatus.ScaStatusCode } - if ic.IacStatusCode == nil || *ic.IacStatusCode == 0 { + if ic.IacStatusCode == nil || (*ic.IacStatusCode == 0 && scanStatus.IacStatusCode != nil) { ic.IacStatusCode = scanStatus.IacStatusCode } - if ic.SecretsStatusCode == nil || *ic.SecretsStatusCode == 0 { + if ic.SecretsStatusCode == nil || (*ic.SecretsStatusCode == 0 && scanStatus.SecretsStatusCode != nil) { ic.SecretsStatusCode = scanStatus.SecretsStatusCode } - if ic.SastStatusCode == nil || *ic.SastStatusCode == 0 { + if ic.SastStatusCode == nil || (*ic.SastStatusCode == 0 && scanStatus.SastStatusCode != nil) { ic.SastStatusCode = scanStatus.SastStatusCode } - if ic.ApplicabilityStatusCode == nil || *ic.ApplicabilityStatusCode == 0 { + if ic.ApplicabilityStatusCode == nil || (*ic.ApplicabilityStatusCode == 0 && scanStatus.ApplicabilityStatusCode != nil) { ic.ApplicabilityStatusCode = scanStatus.ApplicabilityStatusCode } } @@ -109,6 +113,26 @@ func (ic *ScansIssuesCollection) GetScanStatus(scanType utils.SubScanType) *int return nil } +// Only if performed and failed +func (ic *ScansIssuesCollection) HasErrors() bool { + if scaStatus := ic.GetScanStatus(utils.ScaScan); scaStatus != nil && *scaStatus != 0 { + return true + } + if applicabilityStatus := ic.GetScanStatus(utils.ContextualAnalysisScan); applicabilityStatus != nil && *applicabilityStatus != 0 { + return true + } + if iacStatus := ic.GetScanStatus(utils.IacScan); iacStatus != nil && *iacStatus != 0 { + return true + } + if secretsStatus := ic.GetScanStatus(utils.SecretsScan); secretsStatus != nil && *secretsStatus != 0 { + return true + } + if sastStatus := ic.GetScanStatus(utils.SastScan); sastStatus != nil && *sastStatus != 0 { + return true + } + return false +} + func (ic *ScansIssuesCollection) GetScanDetails(scanType utils.SubScanType, violation bool) map[severityutils.Severity]int { scanDetails := map[severityutils.Severity]int{} @@ -178,8 +202,71 @@ func (ic *ScansIssuesCollection) GetTotalIssues(includeSecrets bool) int { return ic.GetTotalVulnerabilities(includeSecrets) + ic.GetTotalViolations(includeSecrets) } -func (ic *ScansIssuesCollection) GetApplicableEvidences() []formats.Evidence { - +type ApplicableEvidences struct { + Evidence formats.Evidence + Severity, FullDetails, IssueId, CveSummary, ImpactedDependency, Remediation string +} + +func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []ApplicableEvidences) { + issueIdToApplicableInfo := map[string]formats.Applicability{} + issueIdToIssue := map[string]formats.VulnerabilityOrViolationRow{} + // Collect evidences from Violations + for _, securityViolation := range ic.ScaViolations { + if securityViolation.Applicable != jasutils.Applicable.String() { + // We only want applicable issues + continue + } + issueId := results.GetIssueIdentifier(securityViolation.Cves, securityViolation.IssueId, "-") + if _, exists := issueIdToIssue[issueId]; exists { + // No need to add the same issue twice + continue + } + for _, cve := range securityViolation.Cves { + if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { + issueIdToIssue[issueId] = securityViolation + issueIdToApplicableInfo[issueId] = *cve.Applicability + } + } + } + // Collect evidences from Vulnerabilities + for _, vulnerability := range ic.ScaVulnerabilities { + if vulnerability.Applicable != jasutils.Applicable.String() { + // We only want applicable issues + continue + } + issueId := results.GetIssueIdentifier(vulnerability.Cves, vulnerability.IssueId, "-") + if _, exists := issueIdToIssue[issueId]; exists { + // No need to add the same issue twice + continue + } + for _, cve := range vulnerability.Cves { + if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { + issueIdToIssue[issueId] = vulnerability + issueIdToApplicableInfo[issueId] = *cve.Applicability + } + } + } + // Create ApplicableEvidences from collected data + for issueId := range maps.Keys(issueIdToApplicableInfo) { + issue := issueIdToIssue[issueId] + applicableInfo := issueIdToApplicableInfo[issueId] + remediation := "" + if issue.JfrogResearchInformation != nil { + remediation = issue.JfrogResearchInformation.Remediation + } + for _, evidence := range applicableInfo.Evidence { + evidences = append(evidences, ApplicableEvidences{ + Evidence: evidence, + Severity: issue.Severity, + FullDetails: applicableInfo.ScannerDescription, + IssueId: results.GetIssueIdentifier(issue.Cves, issue.IssueId, ","), + CveSummary: issue.Summary, + ImpactedDependency: fmt.Sprintf("%s:%s", issue.ImpactedDependencyName, issue.ImpactedDependencyVersion), + Remediation: remediation, + }) + } + } + return } // Violations @@ -206,23 +293,10 @@ func (ic *ScansIssuesCollection) GetTotalVulnerabilities(includeSecrets bool) in return total } - - - +// func (ic *ScansIssuesCollection) GetTotal() // --------------------------------------- - - - - - - - - - - - // func (ic *ScansIssuesCollection) GetScaIssues() (unique []formats.VulnerabilityOrViolationRow) { // return append(ic.ScaVulnerabilities, ic.ScaViolations...) // } @@ -231,7 +305,6 @@ func (ic *ScansIssuesCollection) GetTotalVulnerabilities(includeSecrets bool) in // return getUniqueJasIssues(ic.IacVulnerabilities, ic.IacViolations) // } - // func (ic *ScansIssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { // return getUniqueJasIssues(ic.SecretsVulnerabilities, ic.SecretsViolations) // } diff --git a/utils/issues/issuescollection_test.go b/utils/issues/issuescollection_test.go index 2f93b5b25..4dc18b990 100644 --- a/utils/issues/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -52,6 +52,11 @@ func TestCountIssuesCollectionFindings(t *testing.T) { ScannerDescription: "Secret issue", }, }, + SecretsViolations: []formats.SourceCodeRow{ + { + ScannerDescription: "Secret violation", + }, + }, SastVulnerabilities: []formats.SourceCodeRow{ { ScannerDescription: "Sast issue1", @@ -62,6 +67,10 @@ func TestCountIssuesCollectionFindings(t *testing.T) { }, } - findingsAmount := issuesCollection.CountIssuesCollectionFindings() - assert.Equal(t, 8, findingsAmount) + // With Secrets + findingsAmount := issuesCollection.GetTotalIssues(true) + assert.Equal(t, 9, findingsAmount) + // No Secrets + findingsAmount = issuesCollection.GetTotalIssues(false) + assert.Equal(t, 7, findingsAmount) } diff --git a/utils/outputwriter/icons.go b/utils/outputwriter/icons.go index 3e2326a8d..40426bae3 100644 --- a/utils/outputwriter/icons.go +++ b/utils/outputwriter/icons.go @@ -9,7 +9,8 @@ type ImageSource string type IconName string const ( - baseResourceUrl = "https://raw.githubusercontent.com/jfrog/frogbot/master/resources/" + // baseResourceUrl = "https://raw.githubusercontent.com/jfrog/frogbot/master/resources/" + baseResourceUrl = "https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/" NoVulnerabilityPrBannerSource ImageSource = "v2/noVulnerabilityBannerPR.png" NoVulnerabilityMrBannerSource ImageSource = "v2/noVulnerabilityBannerMR.png" @@ -28,11 +29,11 @@ const ( unknownSeveritySource ImageSource = "v2/applicableUnknownSeverity.png" notApplicableUnknownSeveritySource ImageSource = "v2/notApplicableUnknown.png" - smallCriticalSeveritySource ImageSource = "v2/smallCritical.svg" - smallHighSeveritySource ImageSource = "v2/smallHigh.svg" - smallMediumSeveritySource ImageSource = "v2/smallMedium.svg" - smallLowSeveritySource ImageSource = "v2/smallLow.svg" - smallUnknownSeveritySource ImageSource = "v2/smallUnknown.svg" + smallCriticalSeveritySource ImageSource = "v2/smallCritical.svg" + smallHighSeveritySource ImageSource = "v2/smallHigh.svg" + smallMediumSeveritySource ImageSource = "v2/smallMedium.svg" + smallLowSeveritySource ImageSource = "v2/smallLow.svg" + smallUnknownSeveritySource ImageSource = "v2/smallUnknown.svg" ) func getSeverityTag(iconName IconName, applicability string) string { @@ -49,51 +50,55 @@ func getSmallSeverityTag(iconName IconName) string { func getNotApplicableIconTags(iconName IconName) string { switch strings.ToLower(string(iconName)) { case "critical": - return GetIconTag(notApplicableCriticalSeveritySource) + "
" + return GetIconTag(notApplicableCriticalSeveritySource, "critical (not applicable)") + "
" case "high": - return GetIconTag(notApplicableHighSeveritySource) + "
" + return GetIconTag(notApplicableHighSeveritySource, "high (not applicable)") + "
" case "medium": - return GetIconTag(notApplicableMediumSeveritySource) + "
" + return GetIconTag(notApplicableMediumSeveritySource, "medium (not applicable)") + "
" case "low": - return GetIconTag(notApplicableLowSeveritySource) + "
" + return GetIconTag(notApplicableLowSeveritySource, "low (not applicable)") + "
" } - return GetIconTag(notApplicableUnknownSeveritySource) + "
" + return GetIconTag(notApplicableUnknownSeveritySource, "unknown (not applicable)") + "
" } func getApplicableIconTags(iconName IconName) string { switch strings.ToLower(string(iconName)) { case "critical": - return GetIconTag(criticalSeveritySource) + "
" + return GetIconTag(criticalSeveritySource, "critical") + "
" case "high": - return GetIconTag(highSeveritySource) + "
" + return GetIconTag(highSeveritySource, "high") + "
" case "medium": - return GetIconTag(mediumSeveritySource) + "
" + return GetIconTag(mediumSeveritySource, "medium") + "
" case "low": - return GetIconTag(lowSeveritySource) + "
" + return GetIconTag(lowSeveritySource, "low") + "
" } - return GetIconTag(unknownSeveritySource) + "
" + return GetIconTag(unknownSeveritySource, "unknown") + "
" } func getSmallApplicableIconTags(iconName IconName) string { switch strings.ToLower(string(iconName)) { case "critical": - return GetIconTag(smallCriticalSeveritySource) + " " + return GetImgTag(smallCriticalSeveritySource, "critical") case "high": - return GetIconTag(smallHighSeveritySource) + " " + return GetImgTag(smallHighSeveritySource, "high") case "medium": - return GetIconTag(smallMediumSeveritySource) + " " + return GetImgTag(smallMediumSeveritySource, "medium") case "low": - return GetIconTag(smallLowSeveritySource) + " " + return GetImgTag(smallLowSeveritySource, "low") } - return GetIconTag(smallUnknownSeveritySource) + " " + return GetImgTag(smallUnknownSeveritySource, "unknown") } func GetBanner(banner ImageSource) string { - return GetMarkdownCenterTag(MarkAsLink(GetIconTag(banner), FrogbotDocumentationUrl)) + return GetMarkdownCenterTag(MarkAsLink(GetIconTag(banner, GetSimplifiedTitle(banner)), FrogbotDocumentationUrl)) } -func GetIconTag(imageSource ImageSource) string { - return fmt.Sprintf("!%s", MarkAsLink(GetSimplifiedTitle(imageSource), fmt.Sprintf("%s%s", baseResourceUrl, imageSource))) +func GetIconTag(imageSource ImageSource, alt string) string { + return fmt.Sprintf("!%s", MarkAsLink(alt, fmt.Sprintf("%s%s", baseResourceUrl, imageSource))) +} + +func GetImgTag(imageSource ImageSource, alt string) string { + return fmt.Sprintf("\"%s\"/", baseResourceUrl, imageSource, alt) } func GetSimplifiedTitle(is ImageSource) string { diff --git a/utils/outputwriter/markdowntable.go b/utils/outputwriter/markdowntable.go index 1ac5c4664..32ca8eadc 100644 --- a/utils/outputwriter/markdowntable.go +++ b/utils/outputwriter/markdowntable.go @@ -6,11 +6,22 @@ import ( ) const ( - tableRowFirstColumnSeparator = "| :---------------------: |" - tableRowColumnSeparator = " :-----------------------------------: |" - cellFirstCellPlaceholder = "| %s |" - cellCellPlaceholder = " %s |" - cellDefaultValue = "-" + + // tableRowFirstColumnSeparator = "| :---------------------: |" + // tableRowColumnSeparator = " :-----------------------------------: |" + // cellFirstCellPlaceholder = "| %s |" + // cellCellPlaceholder = " %s |" + + cellDefaultValue = "-" + + firstCellPlaceholder = "| %s |" + cellPlaceholder = " %s |" + + centeredFirstColumnSeparator = "| :---------------------: |" + centeredColumnSeparator = " :-----------------------------------: |" + + defaultFirstColumnSeparator = "| --------------------- |" + defaultColumnSeparator = " ----------------------------------- |" // (Default value for columns) If more than one value exists in a cell, the values will be separated by the delimiter. SeparatorDelimited MarkdownColumnType = "single" @@ -32,6 +43,7 @@ type MarkdownColumnType string type MarkdownColumn struct { Name string + Centered bool ColumnType MarkdownColumnType DefaultValue string } @@ -51,21 +63,21 @@ func NewCellData(values ...string) CellData { func NewMarkdownTable(columns ...string) *MarkdownTableBuilder { columnsInfo := []*MarkdownColumn{} for _, column := range columns { - columnsInfo = append(columnsInfo, NewMarkdownTableSingleValueColumn(column, cellDefaultValue)) + columnsInfo = append(columnsInfo, NewMarkdownTableSingleValueColumn(column, cellDefaultValue, true)) } - return NewMarkdownTableWithColumns(columnsInfo) + return NewMarkdownTableWithColumns(columnsInfo...) } -func NewMarkdownTableWithColumns(columnsInfo []*MarkdownColumn) *MarkdownTableBuilder { +func NewMarkdownTableWithColumns(columnsInfo ...*MarkdownColumn) *MarkdownTableBuilder { return &MarkdownTableBuilder{columns: columnsInfo, delimiter: simpleSeparator} } -func NewMarkdownTableSingleValueColumn(name, defaultValue string) *MarkdownColumn { - return &MarkdownColumn{Name: name, ColumnType: SeparatorDelimited, DefaultValue: defaultValue} +func NewMarkdownTableSingleValueColumn(name, defaultValue string, centered bool) *MarkdownColumn { + return &MarkdownColumn{Name: name, ColumnType: SeparatorDelimited, DefaultValue: defaultValue, Centered: centered} } -func NewMarkdownTableMultiValueColumn(name, defaultValue string) *MarkdownColumn { - return &MarkdownColumn{Name: name, ColumnType: MultiRowColumn, DefaultValue: defaultValue} +func NewMarkdownTableMultiValueColumn(name, defaultValue string, centered bool) *MarkdownColumn { + return &MarkdownColumn{Name: name, ColumnType: MultiRowColumn, DefaultValue: defaultValue, Centered: centered} } // Set the delimiter that will be used to separate multiple values in a cell. @@ -126,18 +138,26 @@ func (t *MarkdownTableBuilder) Build() string { // Header for c, column := range t.columns { if c == 0 { - tableBuilder.WriteString(fmt.Sprintf(cellFirstCellPlaceholder, column.Name)) + tableBuilder.WriteString(fmt.Sprintf(firstCellPlaceholder, column.Name)) } else { - tableBuilder.WriteString(fmt.Sprintf(cellCellPlaceholder, column.Name)) + tableBuilder.WriteString(fmt.Sprintf(cellPlaceholder, column.Name)) } } tableBuilder.WriteString("\n") // Separator - for c := range t.columns { + for c, column := range t.columns { if c == 0 { - tableBuilder.WriteString(tableRowFirstColumnSeparator) + columnSeparator := defaultFirstColumnSeparator + if column.Centered { + columnSeparator = centeredFirstColumnSeparator + } + tableBuilder.WriteString(columnSeparator) } else { - tableBuilder.WriteString(tableRowColumnSeparator) + columnSeparator := defaultColumnSeparator + if column.Centered { + columnSeparator = centeredColumnSeparator + } + tableBuilder.WriteString(columnSeparator) } } // Content diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 9007f1ba0..4e0c602c8 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -17,12 +17,21 @@ const ( FrogbotTitlePrefix = "[🐸 Frogbot]" FrogbotRepoUrl = "https://github.com/jfrog/frogbot" FrogbotDocumentationUrl = "https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot" + JfrogSupportUrl = "https://jfrog.com/support/" ReviewCommentId = "FrogbotReviewComment" scanSummaryTitle = "📗 Scan Summary" issuesDetailsSubTitle = "🔖 Details" jfrogResearchDetailsSubTitle = "🔬 JFrog Research Details" + jobErrorTitle = "⚠️ Error Details" + + // Violation Contexts text + NoViolations = "Vulnerabilities" + WatchViolations = "watch violations" + ProjectViolations = "project violations" + GitRepoViolations = "Git repository violations" + policyViolationTitle = "🚥 Policy Violations" securityViolationTitle = "🚨 Security Violations" licenseViolationTitle = "⚖️ License Violations" @@ -148,36 +157,69 @@ func footer(writer OutputWriter) string { return fmt.Sprintf("%s\n%s", SectionDivider(), writer.MarkInCenter(CommentGeneratedByFrogbot)) } +func GetFrogbotErrorCommentContent(contentForComments []string, err error, writer OutputWriter) (comments []string) { + // First decorate with error suffix, then wrap content with the base decorator + return ConvertContentToComments(contentForComments, writer, getFrogbotErrorSuffixDecorator(writer, err), GetFrogbotCommentBaseDecorator(writer)) +} + +// Adding markdown prefix to identify Frogbot comment and a footer with the link to the documentation +func getFrogbotErrorSuffixDecorator(writer OutputWriter, err error) CommentDecorator { + return func(_ int, content string) string { + var comment strings.Builder + WriteNewLine(&comment) + // Error + WriteContent(&comment, writer.MarkAsTitle("Error:", 4), err.Error()) + WriteNewLine(&comment) + // Action steps + WriteContent(&comment, + writer.MarkAsTitle("Next Steps:", 4), + "1. Please try to rerun the scan.", + fmt.Sprintf("2. If the issue persists, consider checking the %s for troubleshooting tips.", MarkAsLink("Frogbot documentation", FrogbotDocumentationUrl)), + fmt.Sprintf("3. If you still need assistance, feel free to reach out to %s.", MarkAsLink("JFrog Support", JfrogSupportUrl)), + ) + WriteNewLine(&comment) + WriteContent(&comment, "Thank you for your understanding!") + + return content + writer.MarkAsDetails(jobErrorTitle, 3, comment.String()) + } +} + // Summary content func ScanSummaryContent(issues issues.ScansIssuesCollection, violationContext string, includeSecrets bool, writer OutputWriter) string { - if !issues.IssuesExists(includeSecrets) { + if !issues.IssuesExists(includeSecrets) && !issues.HasErrors() { return "" } var contentBuilder strings.Builder totalIssues := issues.GetTotalVulnerabilities(includeSecrets) violations := false - if violationContext != "" { + if violationContext != "" && violationContext != NoViolations { totalIssues = issues.GetTotalViolations(includeSecrets) violations = true } + title := MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", violationContext, totalIssues)) + if issues.HasErrors() { + title = MarkAsBullet(fmt.Sprintf("Frogbot attempted to scan for %s but encountered an error.", violationContext)) + } // Title - WriteContent(&contentBuilder, - writer.MarkAsTitle(scanSummaryTitle, 2), - MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", getIssueType(violations), totalIssues)), - ) + WriteContent(&contentBuilder, writer.MarkAsTitle(scanSummaryTitle, 2), title) + WriteNewLine(&contentBuilder) // Create table, a row for each sub scans summary secretsDetails := "" if includeSecrets { secretsDetails = getScanSecurityIssuesDetails(issues, utils.SecretsScan, violations, writer) } - table := NewMarkdownTable("Scan Category", "Status", "Security Issues") + table := NewMarkdownTableWithColumns( + NewMarkdownTableSingleValueColumn("Scan Category", "⚠️", false), + NewMarkdownTableSingleValueColumn("Status", "⚠️", true), + NewMarkdownTableSingleValueColumn("Security Issues", "-", false), + ) table.AddRow(MarkAsBold("Software Composition Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.ScaScan, violations, writer)) table.AddRow(MarkAsBold("Contextual Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ContextualAnalysisScan)), "") table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.SastScan, violations, writer)) table.AddRow(MarkAsBold("Secrets"), getSubScanResultStatus(issues.GetScanStatus(utils.SecretsScan)), secretsDetails) table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), getSubScanResultStatus(issues.GetScanStatus(utils.IacScan)), getScanSecurityIssuesDetails(issues, utils.IacScan, violations, writer)) - WriteContent(&contentBuilder, writer.MarkInCenter(table.Build())) + WriteContent(&contentBuilder, table.Build()) return contentBuilder.String() } @@ -192,7 +234,7 @@ func getSubScanResultStatus(scanStatusCode *int) string { } func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType utils.SubScanType, violation bool, writer OutputWriter) string { - if issues.IsScanNotCompleted(scanType) { + if issues.HasErrors() || issues.IsScanNotCompleted(scanType) { // Failed/Not scanned, no need to show the details return "" } @@ -207,12 +249,13 @@ func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType case utils.IacScan: severityCountMap = issues.GetScanDetails(utils.IacScan, violation) } - if len(severityCountMap) == 0 { + totalIssues := getTotalIssues(severityCountMap) + if totalIssues == 0 { // No Issues return "Not Found" } var contentBuilder strings.Builder - WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%d Issues Found", getTotalIssues(severityCountMap)), 3, toSeverityDetails(severityCountMap))) + WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%d Issues Found", totalIssues), 3, toSeverityDetails(severityCountMap, writer))) return contentBuilder.String() } @@ -223,23 +266,16 @@ func getTotalIssues(severities map[severityutils.Severity]int) (total int) { return } -func toSeverityDetails(severities map[severityutils.Severity]int) string { +func toSeverityDetails(severities map[severityutils.Severity]int, writer OutputWriter) string { var contentBuilder strings.Builder - // Get severities with values and write them sorted (Critical, High, Medium, Low, Unknown) - if count, ok := severities[severityutils.Critical]; ok && count > 0 { - WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.Critical), count, severityutils.Critical.String())) - } - if count, ok := severities[severityutils.High]; ok && count > 0 { - WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.High), count, severityutils.High.String())) - } - if count, ok := severities[severityutils.Medium]; ok && count > 0 { - WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.Medium), count, severityutils.Medium.String())) - } - if count, ok := severities[severityutils.Low]; ok && count > 0 { - WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.Low), count, severityutils.Low.String())) - } - if count, ok := severities[severityutils.Unknown]; ok && count > 0 { - WriteContent(&contentBuilder, fmt.Sprintf("%s %d %s", severityutils.GetSeverityIcon(severityutils.Unknown), count, severityutils.Unknown.String())) + sortedSeverities := []severityutils.Severity{severityutils.Critical, severityutils.High, severityutils.Medium, severityutils.Low, severityutils.Unknown} + for _, severity := range sortedSeverities { + if count, ok := severities[severity]; ok && count > 0 { + if contentBuilder.Len() > 0 { + contentBuilder.WriteString(writer.Separator()) + } + contentBuilder.WriteString(fmt.Sprintf("%s %d %s", writer.SeverityIcon(severity), count, severity.String())) + } } return contentBuilder.String() } @@ -302,7 +338,7 @@ func getSecurityViolationsSummaryTable(violations []formats.VulnerabilityOrViola } // Construct rows for _, violation := range violations { - row := []CellData{{writer.FormattedSeverity(violation.Severity, violation.Applicable, false)}, getCveIdsCellData(violation.Cves, violation.IssueId)} + row := []CellData{{writer.FormattedSeverity(violation.Severity, violation.Applicable)}, getCveIdsCellData(violation.Cves, violation.IssueId)} if writer.IsShowingCaColumn() { row = append(row, NewCellData(violation.Applicable)) } @@ -347,7 +383,7 @@ func getLicenseViolationsSummaryTable(licenses []formats.LicenseViolationRow, wr } for _, license := range licenses { table.AddRowWithCellData( - NewCellData(writer.FormattedSeverity(license.Severity, "Applicable", false)), + NewCellData(writer.FormattedSeverity(license.Severity, "Applicable")), NewCellData(license.LicenseKey), getDirectDependenciesCellData(license.Components), NewCellData(fmt.Sprintf("%s:%s", license.ImpactedDependencyName, license.ImpactedDependencyVersion)), @@ -428,7 +464,7 @@ func getVulnerabilitiesSummaryTable(vulnerabilities []formats.VulnerabilityOrVio } // Construct rows for _, vulnerability := range vulnerabilities { - row := []CellData{{writer.FormattedSeverity(vulnerability.Severity, vulnerability.Applicable, false)}, getCveIdsCellData(vulnerability.Cves, vulnerability.IssueId)} + row := []CellData{{writer.FormattedSeverity(vulnerability.Severity, vulnerability.Applicable)}, getCveIdsCellData(vulnerability.Cves, vulnerability.IssueId)} if writer.IsShowingCaColumn() { row = append(row, NewCellData(vulnerability.Applicable)) } @@ -444,22 +480,22 @@ func getVulnerabilitiesSummaryTable(vulnerabilities []formats.VulnerabilityOrVio // Applicable CVE Evidence -func ApplicableCveReviewContent(severity, finding, fullDetails, cve, cveDetails, impactedDependency, remediation string, writer OutputWriter) string { +func ApplicableCveReviewContent(issue issues.ApplicableEvidences, writer OutputWriter) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, writer.MarkAsTitle(contextualAnalysisTitle, 2), - writer.MarkInCenter(GetApplicabilityDescriptionTable(severity, cve, impactedDependency, finding, writer)), - writer.MarkAsDetails("Description", 3, fullDetails), - writer.MarkAsDetails("CVE details", 3, cveDetails), + writer.MarkInCenter(GetApplicabilityDescriptionTable(issue.Severity, issue.IssueId, issue.ImpactedDependency, issue.Evidence.Reason, writer)), + writer.MarkAsDetails("Description", 3, issue.FullDetails), + writer.MarkAsDetails("CVE details", 3, issue.CveSummary), ) - if len(remediation) > 0 { - WriteContent(&contentBuilder, writer.MarkAsDetails("Remediation", 3, remediation)) + if len(issue.Remediation) > 0 { + WriteContent(&contentBuilder, writer.MarkAsDetails("Remediation", 3, issue.Remediation)) } return contentBuilder.String() } -func GetApplicabilityDescriptionTable(severity, cve, impactedDependency, finding string, writer OutputWriter) string { - table := NewMarkdownTable("Severity", "Impacted Dependency", "Finding", "CVE").AddRow(writer.FormattedSeverity(severity, "Applicable", false), impactedDependency, finding, cve) +func GetApplicabilityDescriptionTable(severity, issueId, impactedDependency, finding string, writer OutputWriter) string { + table := NewMarkdownTable("Severity", "ID", "Impacted Dependency", "Finding").AddRow(writer.FormattedSeverity(severity, "Applicable"), issueId, impactedDependency, finding) return table.Build() } @@ -467,7 +503,7 @@ func GetApplicabilityDescriptionTable(severity, cve, impactedDependency, finding func getJasIssueDescriptionTable(issue formats.SourceCodeRow, writer OutputWriter) string { columns := []string{"Severity"} - rowData := []string{writer.FormattedSeverity(issue.Severity, "Applicable", false)} + rowData := []string{writer.FormattedSeverity(issue.Severity, "Applicable")} // Optional issueId column (as stored at the platform) if issue.IssueId != "" { columns = append(columns, "ID") @@ -480,8 +516,10 @@ func getJasIssueDescriptionTable(issue formats.SourceCodeRow, writer OutputWrite func getJasFullDescription(issue formats.SourceCodeRow, violation bool, issueDescTable string, writer OutputWriter) string { var contentBuilder strings.Builder + // Separator + WriteNewLine(&contentBuilder) // Write the vulnerability/violation details - WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%s Details", getIssueType(violation)), 4, issueDescTable)) + WriteContent(&contentBuilder, writer.MarkAsTitle(fmt.Sprintf("%s Details", getIssueType(violation)), 3), issueDescTable) // Separator WriteNewLine(&contentBuilder) // Write the description @@ -575,7 +613,7 @@ func getSecretsDescriptionTable(severity, issueId, finding, status string, write applicability = jasutils.NotApplicable.String() } columns := []string{"Severity"} - rowData := []string{writer.FormattedSeverity(severity, applicability, false)} + rowData := []string{writer.FormattedSeverity(severity, applicability)} // Determine if issueId is provided if issueId != "" { columns = append(columns, "ID") @@ -605,6 +643,7 @@ func getIssueType(violation bool) string { } return "Vulnerability" } + func getDirectDependenciesCellData(components []formats.ComponentRow) (dependencies CellData) { if len(components) == 0 { return NewCellData() @@ -673,6 +712,7 @@ func getComponentIssueIdentifier(key, compName, version, watch string) (id strin func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, violations bool, writer OutputWriter) (content string) { var contentBuilder strings.Builder // Title + WriteNewLine(&contentBuilder) WriteContent(&contentBuilder, writer.MarkAsTitle(fmt.Sprintf("%s Details", getIssueType(violations)), 3)) // Details Table directComponent := []string{} @@ -687,7 +727,8 @@ func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, viola noHeaderTable.AddRow(MarkAsBold("Watch Name:"), issue.Watch) } if issue.JfrogResearchInformation != nil && issue.JfrogResearchInformation.Severity != "" { - noHeaderTable.AddRow(MarkAsBold("Jfrog Research Severity:"), writer.FormattedSeverity(issue.JfrogResearchInformation.Severity, "Applicable", true)) + severity := severityutils.Severity(issue.JfrogResearchInformation.Severity) + noHeaderTable.AddRow(MarkAsBold("Jfrog Research Severity:"), fmt.Sprintf("%s %s", writer.SeverityIcon(severity), severity.String())) } if issue.Applicable != "" { noHeaderTable.AddRow(MarkAsBold("Contextual Analysis:"), issue.Applicable) @@ -704,14 +745,20 @@ func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, viola WriteContent(&contentBuilder, noHeaderTable.Build()) // Summary - if issue.Summary != "" { - WriteContent(&contentBuilder, issue.Summary) + summary := issue.Summary + if issue.JfrogResearchInformation != nil && issue.JfrogResearchInformation.Summary != "" { + summary = issue.JfrogResearchInformation.Summary + } + if summary != "" { + WriteNewLine(&contentBuilder) + WriteContent(&contentBuilder, summary) } // Jfrog Research Details - if issue.JfrogResearchInformation == nil { + if issue.JfrogResearchInformation == nil || (issue.JfrogResearchInformation.Details == "" && issue.JfrogResearchInformation.Remediation == "") { return contentBuilder.String() } + WriteNewLine(&contentBuilder) WriteContent(&contentBuilder, writer.MarkAsTitle(jfrogResearchDetailsSubTitle, 3)) if issue.JfrogResearchInformation.Details != "" { diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index 4352eefd4..75d26c826 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -194,14 +194,14 @@ func TestGetPRSummaryContent(t *testing.T) { func TestScanSummaryContent(t *testing.T) { testCases := []struct { - name string + name string violationContext string - includeSecrets bool - issues issues.ScansIssuesCollection - cases []OutputTestCase + includeSecrets bool + issues issues.ScansIssuesCollection + cases []OutputTestCase }{ { - name: "No issues", + name: "No issues", issues: issues.ScansIssuesCollection{}, cases: []OutputTestCase{ { @@ -618,11 +618,15 @@ func TestGenerateReviewComment(t *testing.T) { func TestApplicableReviewContent(t *testing.T) { testCases := []struct { name string + issue issues.ApplicableEvidences severity, finding, fullDetails, cve, cveDetails, impactedDependency, remediation string cases []OutputTestCase }{ { - name: "Applicable CVE review comment content", + name: "Applicable CVE review comment content", + issue: issues.ApplicableEvidences{ + Severity: "Critical", + }, severity: "Critical", finding: "The vulnerable function flask.Flask.run is called", fullDetails: "The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`.", diff --git a/utils/outputwriter/outputwriter.go b/utils/outputwriter/outputwriter.go index dea7b6e1d..841ae2eb2 100644 --- a/utils/outputwriter/outputwriter.go +++ b/utils/outputwriter/outputwriter.go @@ -6,6 +6,7 @@ import ( "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/froggit-go/vcsutils" + "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -105,7 +106,8 @@ type OutputWriter interface { VcsProvider() vcsutils.VcsProvider SetVcsProvider(provider vcsutils.VcsProvider) // Markdown interface - FormattedSeverity(severity, applicability string, small bool) string + SeverityIcon(severity severityutils.Severity) string + FormattedSeverity(severity, applicability string) string Separator() string MarkInCenter(content string) string MarkAsDetails(summary string, subTitleDepth int, content string) string diff --git a/utils/outputwriter/simplifiedoutput.go b/utils/outputwriter/simplifiedoutput.go index 30d437cb3..ea90d50eb 100644 --- a/utils/outputwriter/simplifiedoutput.go +++ b/utils/outputwriter/simplifiedoutput.go @@ -3,6 +3,8 @@ package outputwriter import ( "fmt" "strings" + + "github.com/jfrog/jfrog-cli-security/utils/severityutils" ) const ( @@ -17,7 +19,11 @@ func (smo *SimplifiedOutput) Separator() string { return simpleSeparator } -func (smo *SimplifiedOutput) FormattedSeverity(severity, _ string, _ bool) string { +func (smo *SimplifiedOutput) SeverityIcon(severity severityutils.Severity) string { + return severityutils.GetSeverityIcon(severity) +} + +func (smo *SimplifiedOutput) FormattedSeverity(severity, _ string) string { return severity } diff --git a/utils/outputwriter/standardoutput.go b/utils/outputwriter/standardoutput.go index 28e5df141..c920dfe25 100644 --- a/utils/outputwriter/standardoutput.go +++ b/utils/outputwriter/standardoutput.go @@ -3,6 +3,8 @@ package outputwriter import ( "fmt" "strings" + + "github.com/jfrog/jfrog-cli-security/utils/severityutils" ) type StandardOutput struct { @@ -13,12 +15,19 @@ func (so *StandardOutput) Separator() string { return "
" } -func (so *StandardOutput) FormattedSeverity(severity, applicability string, small bool) string { - if small { - return fmt.Sprintf("%s%8s", getSmallSeverityTag(IconName(severity)), severity) - } else { - return fmt.Sprintf("%s%8s", getSeverityTag(IconName(severity), applicability), severity) +func (so *StandardOutput) SeverityIcon(severity severityutils.Severity) string { + if !so.hasInternetConnection { + return severityutils.GetSeverityIcon(severity) + } + return getSmallSeverityTag(IconName(severity)) + +} + +func (so *StandardOutput) FormattedSeverity(severity, applicability string) string { + if !so.hasInternetConnection { + return severity } + return fmt.Sprintf("%s%8s", getSeverityTag(IconName(severity), applicability), severity) } func (so *StandardOutput) Image(source ImageSource) string { @@ -34,12 +43,9 @@ func (so *StandardOutput) MarkInCenter(content string) string { func (so *StandardOutput) MarkAsDetails(summary string, subTitleDepth int, content string) string { if summary != "" { - summary = fmt.Sprintf(" %s \n", summary) - } - if subTitleDepth > 0 { - summary += "
\n" + summary = fmt.Sprintf("%s", summary) } - return fmt.Sprintf("
\n%s\n%s\n\n
\n", summary, content) + return fmt.Sprintf("
%s%s
", summary, content) } func (so *StandardOutput) MarkAsTitle(title string, subTitleDepth int) string { diff --git a/utils/outputwriter/standardoutput_test.go b/utils/outputwriter/standardoutput_test.go index b2adf244e..133c01905 100644 --- a/utils/outputwriter/standardoutput_test.go +++ b/utils/outputwriter/standardoutput_test.go @@ -73,7 +73,7 @@ func TestStandardFormattedSeverity(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { smo := &StandardOutput{} - assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability, false)) + assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability)) }) } } diff --git a/utils/params.go b/utils/params.go index 0bf548687..af2a6d12a 100644 --- a/utils/params.go +++ b/utils/params.go @@ -275,11 +275,11 @@ func (s *Scan) setDefaultsIfNeeded() (err error) { } type JFrogPlatform struct { - XrayVersion string - XscVersion string - ViolationContext ViolationContext `yaml:"violationContext,omitempty"` - Watches []string `yaml:"watches,omitempty"` - JFrogProjectKey string `yaml:"jfrogProjectKey,omitempty"` + XrayVersion string + XscVersion string + ViolationContext ViolationContext `yaml:"violationContext,omitempty"` + Watches []string `yaml:"watches,omitempty"` + JFrogProjectKey string `yaml:"jfrogProjectKey,omitempty"` } func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { @@ -297,11 +297,7 @@ func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { err = nil } if jp.ViolationContext == None { - var violationContextStr string - if err = readParamFromEnv(ViolationContextEnv, &violationContextStr); err != nil && !e.IsMissingEnvErr(err) { - return - } - jp.ViolationContext = ViolationContext(violationContextStr) + jp.ViolationContext = ViolationContext(getTrimmedEnv(ViolationContextEnv)) } // Validate or set Default base on other params if jp.ViolationContext == None { @@ -311,6 +307,9 @@ func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { jp.ViolationContext = ProjectContext } } + // if jp.ViolationContext == None { + // jp.ViolationContext = GitRepoContext + // } return } diff --git a/utils/scandetails.go b/utils/scandetails.go index 78a6cb565..cad4f490d 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -204,7 +204,8 @@ func (sc *ScanDetails) RunInstallAndAudit(workDirs ...string) (auditResults *res SetAllowPartialResults(sc.allowPartialResults). SetExclusions(sc.PathExclusions). SetIsRecursiveScan(sc.IsRecursiveScan). - SetUseJas(!sc.DisableJas()) + SetUseJas(!sc.DisableJas()) //. + // SetScansToPerform([]utils.SubScanType{utils.ScaScan, utils.ContextualAnalysisScan, utils.SastScan, utils.SecretsScan, utils.SecretTokenValidationScan}) auditParams := audit.NewAuditParams(). SetWorkingDirs(workDirs). diff --git a/utils/utils.go b/utils/utils.go index 3fb99e1b7..2351ed90a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -51,12 +51,13 @@ const ( ) // ViolationContext is a type for violation context (None,Project,GitRepo) -const( - None ViolationContext = "" // No violation context - WatchContext ViolationContext = "watch violations" - ProjectContext ViolationContext = "project violations" - GitRepoContext ViolationContext = "Git repository violations" +const ( + None ViolationContext = "" // No violation context + WatchContext ViolationContext = "watch" + ProjectContext ViolationContext = "project" + GitRepoContext ViolationContext = "git" ) + type ViolationContext string var ( From 2f0c6bf22dce01d745ac915de32058c8815503d3 Mon Sep 17 00:00:00 2001 From: attiasas Date: Sun, 8 Dec 2024 13:26:19 +0200 Subject: [PATCH 15/34] fix bugs --- utils/issues/issuescollection.go | 3 +++ utils/outputwriter/outputcontent.go | 4 ++-- utils/outputwriter/simplifiedoutput.go | 2 +- utils/outputwriter/simplifiedoutput_test.go | 2 +- utils/outputwriter/standardoutput.go | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index b8d02bd8a..e392fa7b8 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -141,6 +141,9 @@ func (ic *ScansIssuesCollection) GetScanDetails(scanType utils.SubScanType, viol for _, violation := range ic.ScaViolations { scanDetails[severityutils.GetSeverity(violation.Severity)]++ } + for _, violation := range ic.LicensesViolations { + scanDetails[severityutils.GetSeverity(violation.Severity)]++ + } } else { for _, vulnerability := range ic.ScaVulnerabilities { scanDetails[severityutils.GetSeverity(vulnerability.Severity)]++ diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 4e0c602c8..1d9bd5e9c 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -216,7 +216,7 @@ func ScanSummaryContent(issues issues.ScansIssuesCollection, violationContext st ) table.AddRow(MarkAsBold("Software Composition Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.ScaScan, violations, writer)) table.AddRow(MarkAsBold("Contextual Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ContextualAnalysisScan)), "") - table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.SastScan, violations, writer)) + table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.SastScan)), getScanSecurityIssuesDetails(issues, utils.SastScan, violations, writer)) table.AddRow(MarkAsBold("Secrets"), getSubScanResultStatus(issues.GetScanStatus(utils.SecretsScan)), secretsDetails) table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), getSubScanResultStatus(issues.GetScanStatus(utils.IacScan)), getScanSecurityIssuesDetails(issues, utils.IacScan, violations, writer)) WriteContent(&contentBuilder, table.Build()) @@ -255,7 +255,7 @@ func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType return "Not Found" } var contentBuilder strings.Builder - WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%d Issues Found", totalIssues), 3, toSeverityDetails(severityCountMap, writer))) + WriteContent(&contentBuilder, writer.MarkAsDetails(fmt.Sprintf("%d Issues Found", totalIssues), 0, toSeverityDetails(severityCountMap, writer))) return contentBuilder.String() } diff --git a/utils/outputwriter/simplifiedoutput.go b/utils/outputwriter/simplifiedoutput.go index ea90d50eb..1a4cb835a 100644 --- a/utils/outputwriter/simplifiedoutput.go +++ b/utils/outputwriter/simplifiedoutput.go @@ -41,7 +41,7 @@ func (smo *SimplifiedOutput) MarkAsDetails(summary string, subTitleDepth int, co func (smo *SimplifiedOutput) MarkAsTitle(title string, subTitleDepth int) string { if subTitleDepth == 0 { - return fmt.Sprintf("%s\n%s\n%s", SectionDivider(), title, SectionDivider()) + return title } return fmt.Sprintf("%s\n%s %s\n%s", SectionDivider(), strings.Repeat("#", subTitleDepth), title, SectionDivider()) } diff --git a/utils/outputwriter/simplifiedoutput_test.go b/utils/outputwriter/simplifiedoutput_test.go index 82ac0896e..6dc860cfb 100644 --- a/utils/outputwriter/simplifiedoutput_test.go +++ b/utils/outputwriter/simplifiedoutput_test.go @@ -57,7 +57,7 @@ func TestSimpleFormattedSeverity(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { smo := &SimplifiedOutput{} - assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability, false)) + assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability)) }) } } diff --git a/utils/outputwriter/standardoutput.go b/utils/outputwriter/standardoutput.go index c920dfe25..7143f72b7 100644 --- a/utils/outputwriter/standardoutput.go +++ b/utils/outputwriter/standardoutput.go @@ -41,7 +41,7 @@ func (so *StandardOutput) MarkInCenter(content string) string { return GetMarkdownCenterTag(content) } -func (so *StandardOutput) MarkAsDetails(summary string, subTitleDepth int, content string) string { +func (so *StandardOutput) MarkAsDetails(summary string, _ int, content string) string { if summary != "" { summary = fmt.Sprintf("%s", summary) } From b632315cd4fb95902bd52ef1a14414384e71dcb7 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 10 Dec 2024 12:47:26 +0200 Subject: [PATCH 16/34] done implement --- scanpullrequest/scanpullrequest.go | 33 +- ..._review_content_no_code_flow_simplified.md | 23 +- ...st_review_content_no_code_flow_standard.md | 21 +- .../sast/sast_review_content_simplified.md | 25 +- .../sast/sast_review_content_standard.md | 48 +- ...ast_violation_review_content_simplified.md | 54 +- .../sast_violation_review_content_standard.md | 63 +- .../summary/summary_error_simplified.md | 1 + .../summary/summary_error_standard.md | 1 + .../summary/summary_violation_simplified.md | 15 + .../summary/summary_violation_standard.md | 12 + .../security/security_violation_simplified.md | 25 + utils/comment.go | 103 +- utils/comment_test.go | 323 ++++- utils/issues/issuescollection.go | 27 +- utils/issues/issuescollection_test.go | 428 +++++- utils/outputwriter/markdowntable.go | 63 +- utils/outputwriter/markdowntable_test.go | 67 +- utils/outputwriter/outputcontent.go | 420 +++--- utils/outputwriter/outputcontent_test.go | 1145 ++++++++++------- utils/outputwriter/simplifiedoutput.go | 6 +- utils/scandetails.go | 4 +- 22 files changed, 2024 insertions(+), 883 deletions(-) create mode 100644 testdata/messages/summarycomment/summary/summary_error_simplified.md create mode 100644 testdata/messages/summarycomment/summary/summary_error_standard.md create mode 100644 testdata/messages/summarycomment/summary/summary_violation_simplified.md create mode 100644 testdata/messages/summarycomment/summary/summary_violation_standard.md create mode 100644 testdata/messages/summarycomment/violations/security/security_violation_simplified.md diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index d084d9f92..e6a21fc35 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -352,29 +352,30 @@ func getResultScanStatues(includeVulnerabilities, hasViolationContext bool, cmdR } // Returns all the issues found in the source branch that didn't exist in the target branch. -func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*issues.ScansIssuesCollection, error) { - var err error +func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (newIssues *issues.ScansIssuesCollection, err error) { convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext, IncludeLicenses: len(allowedLicenses) > 0, AllowedLicenses: allowedLicenses, SimplifiedOutput: true}) simpleJsonSource, err := convertor.ConvertToSimpleJson(sourceResults) if err != nil { - return nil, err + return } simpleJsonTarget, err := convertor.ConvertToSimpleJson(targetResults) if err != nil { - return nil, err + return } - return &issues.ScansIssuesCollection{ - ScanStatus: getScanStatus(simpleJsonTarget, simpleJsonSource), - ScaVulnerabilities: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities), - ScaViolations: getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.SecurityViolations, simpleJsonSource.SecurityViolations), - IacVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities), - IacViolations: createNewSourceCodeRows(simpleJsonTarget.IacsViolations, simpleJsonSource.IacsViolations), - SecretsVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.SecretsVulnerabilities, simpleJsonSource.SecretsVulnerabilities), - SecretsViolations: createNewSourceCodeRows(simpleJsonTarget.SecretsViolations, simpleJsonSource.SecretsViolations), - SastVulnerabilities: createNewSourceCodeRows(simpleJsonTarget.SastVulnerabilities, simpleJsonSource.SastVulnerabilities), - SastViolations: createNewSourceCodeRows(simpleJsonTarget.SastViolations, simpleJsonSource.SastViolations), - LicensesViolations: getUniqueLicenseRows(simpleJsonTarget.LicensesViolations, simpleJsonSource.LicensesViolations), - }, nil + newIssues = &issues.ScansIssuesCollection{} + newIssues.ScanStatus = getScanStatus(simpleJsonTarget, simpleJsonSource) + // Get the unique sca vulnerabilities and violations between the source and target branches + newIssues.ScaVulnerabilities = getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities) + newIssues.ScaViolations = getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.SecurityViolations, simpleJsonSource.SecurityViolations) + newIssues.LicensesViolations = getUniqueLicenseRows(simpleJsonTarget.LicensesViolations, simpleJsonSource.LicensesViolations) + // Get the unique source code vulnerabilities and violations between the source and target branches + newIssues.IacVulnerabilities = createNewSourceCodeRows(simpleJsonTarget.IacsVulnerabilities, simpleJsonSource.IacsVulnerabilities) + newIssues.IacViolations = createNewSourceCodeRows(simpleJsonTarget.IacsViolations, simpleJsonSource.IacsViolations) + newIssues.SecretsVulnerabilities = createNewSourceCodeRows(simpleJsonTarget.SecretsVulnerabilities, simpleJsonSource.SecretsVulnerabilities) + newIssues.SecretsViolations = createNewSourceCodeRows(simpleJsonTarget.SecretsViolations, simpleJsonSource.SecretsViolations) + newIssues.SastVulnerabilities = createNewSourceCodeRows(simpleJsonTarget.SastVulnerabilities, simpleJsonSource.SastVulnerabilities) + newIssues.SastViolations = createNewSourceCodeRows(simpleJsonTarget.SastViolations, simpleJsonSource.SastViolations) + return } func getScanStatus(cmdResults ...formats.SimpleJsonResults) formats.ScanStatus { diff --git a/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_simplified.md b/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_simplified.md index 73567b6bd..55583c608 100644 --- a/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_simplified.md +++ b/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_simplified.md @@ -6,16 +6,25 @@ --- | Severity | Finding | | :---------------------: | :-----------------------------------: | -| Low | Stack Trace Exposure | +| Low | Found a Use of Insecure Random | + --- ### Full description --- -### Overview -Stack trace exposure is a type of security vulnerability that occurs when a program reveals -sensitive information, such as the names and locations of internal files and variables, -in error messages or other diagnostic output. This can happen when a program crashes or -encounters an error, and the stack trace (a record of the program's call stack at the time -of the error) is included in the output. \ No newline at end of file + + +--- +### Vulnerability Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798, CWE-799 | +| **Rule ID:** | js-insecure-random | + +Scanner Description.... + + diff --git a/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_standard.md b/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_standard.md index eb2cffdbc..6b358b6fb 100644 --- a/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_standard.md +++ b/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_standard.md @@ -4,20 +4,19 @@ | Severity | Finding | | :---------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | Stack Trace Exposure | +| Low | Found a Use of Insecure Random |
-
- Full description -
+
Full description -### Overview -Stack trace exposure is a type of security vulnerability that occurs when a program reveals -sensitive information, such as the names and locations of internal files and variables, -in error messages or other diagnostic output. This can happen when a program crashes or -encounters an error, and the stack trace (a record of the program's call stack at the time -of the error) is included in the output. +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798
CWE-799 | +| **Rule ID:** | js-insecure-random | -
+Scanner Description.... + +
diff --git a/testdata/messages/reviewcomment/sast/sast_review_content_simplified.md b/testdata/messages/reviewcomment/sast/sast_review_content_simplified.md index 366c7c4c2..18e64d62e 100644 --- a/testdata/messages/reviewcomment/sast/sast_review_content_simplified.md +++ b/testdata/messages/reviewcomment/sast/sast_review_content_simplified.md @@ -6,19 +6,28 @@ --- | Severity | Finding | | :---------------------: | :-----------------------------------: | -| Low | Stack Trace Exposure | +| Low | Found a Use of Insecure Random | + --- ### Full description --- -### Overview -Stack trace exposure is a type of security vulnerability that occurs when a program reveals -sensitive information, such as the names and locations of internal files and variables, -in error messages or other diagnostic output. This can happen when a program crashes or -encounters an error, and the stack trace (a record of the program's call stack at the time -of the error) is included in the output. + + +--- +### Vulnerability Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798, CWE-799 | +| **Rule ID:** | js-insecure-random | + +Scanner Description.... + + --- ### Code Flows @@ -31,6 +40,7 @@ of the error) is included in the output. --- + ↘️ `other-snippet` (at file2 line 1) ↘️ `snippet` (at file line 0) @@ -41,6 +51,7 @@ of the error) is included in the output. --- + ↘️ `a-snippet` (at file line 10) ↘️ `snippet` (at file line 0) diff --git a/testdata/messages/reviewcomment/sast/sast_review_content_standard.md b/testdata/messages/reviewcomment/sast/sast_review_content_standard.md index 6794f48e9..f3f955090 100644 --- a/testdata/messages/reviewcomment/sast/sast_review_content_standard.md +++ b/testdata/messages/reviewcomment/sast/sast_review_content_standard.md @@ -4,52 +4,32 @@ | Severity | Finding | | :---------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | Stack Trace Exposure | +| ![low](https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/v2/applicableLowSeverity.png)
Low | Found a Use of Insecure Random |
-
- Full description -
+
Full description -### Overview -Stack trace exposure is a type of security vulnerability that occurs when a program reveals -sensitive information, such as the names and locations of internal files and variables, -in error messages or other diagnostic output. This can happen when a program crashes or -encounters an error, and the stack trace (a record of the program's call stack at the time -of the error) is included in the output. +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798
CWE-799 | +| **Rule ID:** | js-insecure-random | -
- -
- Code Flows -
- - -
- Vulnerable data flow analysis result -
+Scanner Description.... +
+
Code Flows +
Vulnerable data flow analysis result ↘️ `other-snippet` (at file2 line 1) ↘️ `snippet` (at file line 0) - - -
- -
- Vulnerable data flow analysis result -
- +
+
Vulnerable data flow analysis result ↘️ `a-snippet` (at file line 10) ↘️ `snippet` (at file line 0) - - -
- - -
+

\ No newline at end of file diff --git a/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md b/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md index 366c7c4c2..8c3aba08a 100644 --- a/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md +++ b/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md @@ -1,24 +1,35 @@ --- -## 🎯 Static Application Security Testing (SAST) Vulnerability +## 🎯 Static Application Security Testing (SAST) Violation --- -| Severity | Finding | -| :---------------------: | :-----------------------------------: | -| Low | Stack Trace Exposure | +| Severity | ID | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| Low | sast-violation-id | Found a Use of Insecure Random | jas-watch | policy1, policy2 | +| High | sast-violation-id-2 | Found a Use of Insecure Random | jas-watch2 | policy3 | +| High | sast-violation-id-3 | Found An Express Not Using Helmet | jas-watch2 | policy3 | + + +--- +### [ Use of Insecure Random ] + +--- + + --- -### Full description +### Violation Details --- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798, CWE-799 | +| **Rule ID:** | js-insecure-random | + +Scanner Description.... + -### Overview -Stack trace exposure is a type of security vulnerability that occurs when a program reveals -sensitive information, such as the names and locations of internal files and variables, -in error messages or other diagnostic output. This can happen when a program crashes or -encounters an error, and the stack trace (a record of the program's call stack at the time -of the error) is included in the output. --- ### Code Flows @@ -31,6 +42,7 @@ of the error) is included in the output. --- + ↘️ `other-snippet` (at file2 line 1) ↘️ `snippet` (at file line 0) @@ -41,6 +53,26 @@ of the error) is included in the output. --- + ↘️ `a-snippet` (at file line 10) ↘️ `snippet` (at file line 0) + + +--- +### [ Express Not Using Helmet ] + +--- + + + +--- +### Violation Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Rule ID:** | js-express-without-helmet | + +Scanner Description.... + diff --git a/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md b/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md index e4365979b..38e5c7030 100644 --- a/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md +++ b/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md @@ -2,61 +2,46 @@ ## 🎯 Static Application Security Testing (SAST) Violation
-| Severity | ID | Finding | -| :---------------------: | :-----------------------------------: |:-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | VIOLATION_ID | Stack Trace Exposure | +| Severity | ID | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![low](https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/v2/applicableLowSeverity.png)
Low | sast-violation-id | Found a Use of Insecure Random | jas-watch | policy1
policy2 | +| ![high](https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/v2/applicableHighSeverity.png)
High | sast-violation-id-2 | Found a Use of Insecure Random | jas-watch2 | policy3 | +| ![high](https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/v2/applicableHighSeverity.png)
High | sast-violation-id-3 | Found An Express Not Using Helmet | jas-watch2 | policy3 |
-
-Full description -### Violation Details -| | | -| :--- | :--- | -__Policies:__| xsc-policy-1 -__Watch Name:__| xsc-watch -__CWE:__| CWE-89 -__Rule ID:__| java-sql-injection - - -### Overview -Stack trace exposure is a type of security vulnerability that occurs when a program reveals -sensitive information, such as the names and locations of internal files and variables, -in error messages or other diagnostic output. This can happen when a program crashes or -encounters an error, and the stack trace (a record of the program's call stack at the time -of the error) is included in the output. +
[ Use of Insecure Random ] -
- -
- Code Flows -
+### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798
CWE-799 | +| **Rule ID:** | js-insecure-random | +Scanner Description.... -
- Vulnerable data flow analysis result -
+
Code Flows +
Vulnerable data flow analysis result ↘️ `other-snippet` (at file2 line 1) ↘️ `snippet` (at file line 0) - - -
- -
- Vulnerable data flow analysis result -
- +
+
Vulnerable data flow analysis result ↘️ `a-snippet` (at file line 10) ↘️ `snippet` (at file line 0) +


+
[ Express Not Using Helmet ] +### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **Rule ID:** | js-express-without-helmet | -
- +Scanner Description.... -
+
\ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_error_simplified.md b/testdata/messages/summarycomment/summary/summary_error_simplified.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/testdata/messages/summarycomment/summary/summary_error_simplified.md @@ -0,0 +1 @@ + diff --git a/testdata/messages/summarycomment/summary/summary_error_standard.md b/testdata/messages/summarycomment/summary/summary_error_standard.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/testdata/messages/summarycomment/summary/summary_error_standard.md @@ -0,0 +1 @@ + diff --git a/testdata/messages/summarycomment/summary/summary_violation_simplified.md b/testdata/messages/summarycomment/summary/summary_violation_simplified.md new file mode 100644 index 000000000..e989c77da --- /dev/null +++ b/testdata/messages/summarycomment/summary/summary_violation_simplified.md @@ -0,0 +1,15 @@ + + +--- +## 📗 Scan Summary + +--- +- Frogbot scanned for and found 9 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done | 6 Issues Found: ❗️ 1 Critical, 🔴 2 High, 🟠 1 Medium, 🟡 1 Low, ⚪️ 1 Unknown | +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done | 3 Issues Found: 🔴 2 High, 🟡 1 Low | +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_violation_standard.md b/testdata/messages/summarycomment/summary/summary_violation_standard.md new file mode 100644 index 000000000..e6b858601 --- /dev/null +++ b/testdata/messages/summarycomment/summary/summary_violation_standard.md @@ -0,0 +1,12 @@ +## 📗 Scan Summary +- Frogbot scanned for violations and found 17 violations. + +| Scan Category | Status | Security Issues +| :--- | :--- | :--- | +__Software Composition Analysis__ | ✅ Done |
__17 Issues Found__❗️ 5 Critical
🔴 9 High
🟠 3 Medium +__Contextual Analysis__ | ✅ Done +__Static Application Security Testing (SAST)__ | ✅ Done | __Not Found__ +__Secrets__ | ❌ Failed +__Infrastucture as Code (IaC)__ | ℹ️ Not Scanned +
+
diff --git a/testdata/messages/summarycomment/violations/security/security_violation_simplified.md b/testdata/messages/summarycomment/violations/security/security_violation_simplified.md new file mode 100644 index 000000000..b9e45ec1b --- /dev/null +++ b/testdata/messages/summarycomment/violations/security/security_violation_simplified.md @@ -0,0 +1,25 @@ + +## 🚥 Policy Violations + +### 🚨 Security Violations + +
+ +| Severity/Risk | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Watch Name | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | sca-watch | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | + +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | CVE-2022-26652
CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | +| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | + +
+ + +### 🔖 Details + +
+ [ CVE-1111-11111 ] impacted 3.0.0 (sca-watch) + +### Violation Details\n\n| | |\n| :--- | :--- |\n__Policies:__| xsc-policy-1\n__Watch Name:__| xsc-watch\n__JFrog Research Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/critical.svg) Critical\n__Contextual Analysis:__| Applicable\n__Direct Dependencies:__| flask:1.1.2\n__Impacted Dependency:__| werkzeug:1.0.1\n__Fix Versions:__| 4.0.0, 5.0.0\n__CVSS V3:__| 9.8\n\nsome-summary\n\n### 🔬 JFrog Rese +arch Details\n\n**Description:**\nSummary XRAY-122345\n\n**Remediation:**\nsome remediation\n\n
\n\n
\n [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n\n**Description:**\nSummary XRAY-122345\n\n**Remediation:**\nsome remediation\n\n
\n\n
\n [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 \n
\n\n\n**Remediation:**\nsome remediation\n\n
\n\n
\n github.com/mholt/archiver/v3 v3.5.1 \n
\n\n\n**Description:**\nSummary\n\n
\n \ No newline at end of file diff --git a/utils/comment.go b/utils/comment.go index 182494641..3c8b01380 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -223,31 +223,86 @@ func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection } // IAC review comments for _, iac := range issues.IacVulnerabilities { - commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, iac.Location, generateSourceCodeVulnerabilityReviewContent(IacComment, iac, writer))) + commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, iac.Location, generateSourceCodeReviewContent(IacComment, false, writer, iac))) } - for _, iac := range issues.IacViolations { - commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, iac.Location, generateSourceCodeViolationReviewContent(IacComment, iac, writer))) + for _, similarIacIssues := range groupSimilarJasIssues(issues.IacViolations) { + commentsToAdd = append(commentsToAdd, generateReviewComment(IacComment, similarIacIssues.Location, generateSourceCodeReviewContent(IacComment, true, writer, similarIacIssues.issues...))) } // SAST review comments for _, sast := range issues.SastVulnerabilities { - commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeVulnerabilityReviewContent(SastComment, sast, writer))) + commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeReviewContent(SastComment, false, writer, sast))) } - for _, sast := range issues.SastViolations { - commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, sast.Location, generateSourceCodeViolationReviewContent(SastComment, sast, writer))) + if len(issues.SastViolations) > 0 { + for _, similarSastIssues := range groupSimilarJasIssues(issues.SastViolations) { + commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, similarSastIssues.Location, generateSourceCodeReviewContent(SastComment, true, writer, similarSastIssues.issues...))) + } } // Secrets review comments if !repo.Params.PullRequestSecretComments { return } for _, secret := range issues.SecretsVulnerabilities { - commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeVulnerabilityReviewContent(SecretComment, secret, writer))) + commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeReviewContent(SecretComment, false, writer, secret))) + } + if len(issues.SecretsViolations) > 0 { + for _, similarSecretsIssues := range groupSimilarJasIssues(issues.SecretsViolations) { + commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, similarSecretsIssues.Location, generateSourceCodeReviewContent(SecretComment, true, writer, similarSecretsIssues.issues...))) + } + } + return +} + +type similarIssues struct { + formats.Location + issues []formats.SourceCodeRow +} + +// For JAS violations we can have similar issues at the same location from different watches, we need to group similar issues to add them to the same comment. +func groupSimilarJasIssues(issues []formats.SourceCodeRow) (groupedIssues []similarIssues) { + idToIssues := make(map[string]similarIssues) + for _, issue := range issues { + id := getSourceCodeRowId(issue) + if similarIssue, ok := idToIssues[id]; ok { + similarIssue.issues = append(similarIssue.issues, issue) + idToIssues[id] = similarIssue + continue + } + idToIssues[id] = similarIssues{ + Location: issue.Location, + issues: []formats.SourceCodeRow{issue}, + } } - for _, secret := range issues.SecretsViolations { - commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, secret.Location, generateSourceCodeViolationReviewContent(SecretComment, secret, writer))) + for _, similarIssue := range idToIssues { + groupedIssues = append(groupedIssues, similarIssue) } return } +// // We group issues by their watches, so we can add all the watches to the same comment. +// func groupSimilarIssues(issues []formats.SourceCodeRow) (groupedIssues []formats.SourceCodeRow, issuesWatches map[string][]formats.ViolationContext) { +// issuesWatches = make(map[string][]formats.ViolationContext) +// for _, issue := range issues { +// if issue.Watch == "" { +// // no violation context, just add to the list +// groupedIssues = append(groupedIssues, issue) +// continue +// } +// id := getSourceCodeRowId(issue) +// if watches, ok := issuesWatches[id]; ok { +// issuesWatches[id] = append(watches, issue.ViolationContext) +// continue +// } +// groupedIssues = append(groupedIssues, issue) +// issuesWatches[id] = []formats.ViolationContext{issue.ViolationContext} +// } +// return groupedIssues, issuesWatches +// } + +// We show different comments for each location and rule ID. (we group similar issues/violations to the same comment) +func getSourceCodeRowId(issue formats.SourceCodeRow) string { + return issue.RuleId + issue.Location.ToString() +} + func generateReviewComment(commentType ReviewCommentType, location formats.Location, content string) (comment ReviewComment) { return ReviewComment{ Location: location, @@ -266,26 +321,26 @@ func generateApplicabilityReviewContent(issue issues.ApplicableEvidences, writer return outputwriter.GenerateReviewCommentContent(outputwriter.ApplicableCveReviewContent(issue, writer), writer) } -func generateSourceCodeVulnerabilityReviewContent(commentType ReviewCommentType, issue formats.SourceCodeRow, writer outputwriter.OutputWriter) (content string) { - switch commentType { - case IacComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(issue, false, writer), writer) - case SastComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(issue, false, writer), writer) - case SecretComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(issue, false, writer), writer) - } - return -} +// func generateSourceCodeVulnerabilityReviewContent(commentType ReviewCommentType, issue formats.SourceCodeRow, writer outputwriter.OutputWriter) (content string) { +// switch commentType { +// case IacComment: +// return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(issue, writer), writer) +// case SastComment: +// return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(issue, writer), writer) +// case SecretComment: +// return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(issue, writer), writer) +// } +// return +// } -func generateSourceCodeViolationReviewContent(commentType ReviewCommentType, issue formats.SourceCodeRow, writer outputwriter.OutputWriter) (content string) { +func generateSourceCodeReviewContent(commentType ReviewCommentType, violation bool, writer outputwriter.OutputWriter, similarIssues ...formats.SourceCodeRow) (content string) { switch commentType { case IacComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(issue, true, writer), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(violation, writer, similarIssues...), writer) case SastComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(issue, true, writer), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(violation, writer, similarIssues...), writer) case SecretComment: - return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(issue, true, writer), writer) + return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(violation, writer, similarIssues...), writer) } return } diff --git a/utils/comment_test.go b/utils/comment_test.go index baa554567..79790c3f6 100644 --- a/utils/comment_test.go +++ b/utils/comment_test.go @@ -50,6 +50,159 @@ func TestGetFrogbotReviewComments(t *testing.T) { } } +func TestGroupSimilarJasIssues(t *testing.T) { + testCases := []struct { + name string + issues []formats.SourceCodeRow + groupedIssues []similarIssues + }{ + { + name: "No issues", + }, + { + name: "Single issue", + issues: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding1", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + }, + groupedIssues: []similarIssues{ + { + formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding1", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + }, + }, + }, + }, + { + name: "Multiple issues - no similar issues", + issues: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding1", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding1", + ScannerInfo: formats.ScannerInfo{RuleId: "rule2"}, + }, + }, + groupedIssues: []similarIssues{ + { + formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding1", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + }, + }, + { + formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding1", + ScannerInfo: formats.ScannerInfo{RuleId: "rule2"}, + }, + }, + }, + }, + }, + { + name: "Multiple issues - similar issues", + issues: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding1", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + { + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding2", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + { + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding3", + ScannerInfo: formats.ScannerInfo{RuleId: "rule2"}, + }, + { + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Location: formats.Location{File: "file2", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding2", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + }, + groupedIssues: []similarIssues{ + { + formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding1", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + { + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding2", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + }, + }, + { + formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding3", + ScannerInfo: formats.ScannerInfo{RuleId: "rule2"}, + }, + }, + }, + { + formats.Location{File: "file2", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Location: formats.Location{File: "file2", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, + Finding: "finding2", + ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, + }, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + output := groupSimilarJasIssues(tc.issues) + assert.ElementsMatch(t, tc.groupedIssues, output) + }) + } +} + func TestGetNewReviewComments(t *testing.T) { writer := &outputwriter.StandardOutput{} @@ -83,6 +236,9 @@ func TestGetNewReviewComments(t *testing.T) { SeverityNumValue: 13, }, Finding: "Secret", + ScannerInfo: formats.ScannerInfo{ + RuleId: "secret-rule", + }, Location: formats.Location{ File: "index.js", StartLine: 5, @@ -119,7 +275,89 @@ func TestGetNewReviewComments(t *testing.T) { Severity: "High", SeverityNumValue: 13, }, - IssueId: "id", + Finding: "secret finding", + Applicability: &formats.Applicability{Status: "Inactive"}, + ScannerInfo: formats.ScannerInfo{ + RuleId: "secret-rule", + }, + Location: formats.Location{ + File: "index.js", + StartLine: 5, + StartColumn: 6, + EndLine: 7, + EndColumn: 8, + Snippet: "access token exposed", + }, + }, + }, + }, + expectedOutput: []ReviewComment{ + { + Location: formats.Location{ + File: "index.js", + StartLine: 5, + StartColumn: 6, + EndLine: 7, + EndColumn: 8, + Snippet: "access token exposed", + }, + Type: SecretComment, + CommentInfo: vcsclient.PullRequestComment{ + CommentInfo: vcsclient.CommentInfo{ + Content: outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(false, writer, formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "secret-rule", + }, + Finding: "secret finding", + Applicability: &formats.Applicability{Status: "Inactive"}, + }), writer), + }, + PullRequestDiff: vcsclient.PullRequestDiff{ + OriginalFilePath: "index.js", + OriginalStartLine: 5, + OriginalStartColumn: 6, + OriginalEndLine: 7, + OriginalEndColumn: 8, + NewFilePath: "index.js", + NewStartLine: 5, + NewStartColumn: 6, + NewEndLine: 7, + NewEndColumn: 8, + }, + }, + }, + }, + }, + { + name: "Multiple violations, one review comments", + generateSecretsComments: true, + issues: &issues.ScansIssuesCollection{ + ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ + { + Summary: "summary-2", + Applicable: "Applicable", + IssueId: "XRAY-2", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: formats.SeverityDetails{Severity: "low"}, + ImpactedDependencyName: "component-C", + }, + Cves: []formats.CveRow{{Id: "CVE-2023-4321"}}, + Technology: techutils.Npm, + }, + }, + SecretsViolations: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "secret-rule", + }, Finding: "secret finding", Applicability: &formats.Applicability{Status: "Inactive"}, Location: formats.Location{ @@ -130,6 +368,31 @@ func TestGetNewReviewComments(t *testing.T) { EndColumn: 8, Snippet: "access token exposed", }, + ViolationContext: formats.ViolationContext{ + Watch: "watch", + }, + }, + { + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "secret-rule", + }, + Finding: "secret finding", + Applicability: &formats.Applicability{Status: "Inactive"}, + Location: formats.Location{ + File: "index.js", + StartLine: 5, + StartColumn: 6, + EndLine: 7, + EndColumn: 8, + Snippet: "access token exposed", + }, + ViolationContext: formats.ViolationContext{ + Watch: "watch2", + }, }, }, }, @@ -146,7 +409,30 @@ func TestGetNewReviewComments(t *testing.T) { Type: SecretComment, CommentInfo: vcsclient.PullRequestComment{ CommentInfo: vcsclient.CommentInfo{ - Content: outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent("High", "id", "secret finding", "", "Inactive", writer), writer), + Content: outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(true, writer, + formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "High", SeverityNumValue: 13}, + ScannerInfo: formats.ScannerInfo{ + RuleId: "secret-rule", + }, + Finding: "secret finding", + Applicability: &formats.Applicability{Status: "Inactive"}, + ViolationContext: formats.ViolationContext{ + Watch: "watch", + }, + }, + formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{Severity: "High", SeverityNumValue: 13}, + ScannerInfo: formats.ScannerInfo{ + RuleId: "secret-rule", + }, + Finding: "secret finding", + Applicability: &formats.Applicability{Status: "Inactive"}, + ViolationContext: formats.ViolationContext{ + Watch: "watch2", + }, + }, + ), writer), }, PullRequestDiff: vcsclient.PullRequestDiff{ OriginalFilePath: "index.js", @@ -186,6 +472,9 @@ func TestGetNewReviewComments(t *testing.T) { Severity: "High", SeverityNumValue: 13, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "aws-violation", + }, Finding: "Missing auto upgrade was detected", Location: formats.Location{ File: "file1", @@ -203,6 +492,9 @@ func TestGetNewReviewComments(t *testing.T) { Severity: "High", SeverityNumValue: 13, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "sast-rule", + }, Finding: "XSS Vulnerability", Location: formats.Location{ File: "file1", @@ -228,7 +520,10 @@ func TestGetNewReviewComments(t *testing.T) { Type: ApplicableComment, CommentInfo: vcsclient.PullRequestComment{ CommentInfo: vcsclient.CommentInfo{ - Content: outputwriter.GenerateReviewCommentContent(outputwriter.ApplicableCveReviewContent("Low", "", "", "CVE-2023-4321", "summary-2", "component-C:", "", writer), writer), + Content: outputwriter.GenerateReviewCommentContent(outputwriter.ApplicableCveReviewContent(issues.ApplicableEvidences{ + Evidence: formats.Evidence{Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}, + Severity: "Low", IssueId: "CVE-2023-4321", CveSummary: "summary-2", ImpactedDependency: "component-C", + }, writer), writer), }, PullRequestDiff: vcsclient.PullRequestDiff{ OriginalFilePath: "file1", @@ -256,7 +551,16 @@ func TestGetNewReviewComments(t *testing.T) { Type: IacComment, CommentInfo: vcsclient.PullRequestComment{ CommentInfo: vcsclient.CommentInfo{ - Content: outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent("High", "Missing auto upgrade was detected", "", writer), writer), + Content: outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(false, writer, formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "aws-violation", + }, + Finding: "Missing auto upgrade was detected", + }), writer), }, PullRequestDiff: vcsclient.PullRequestDiff{ OriginalFilePath: "file1", @@ -284,7 +588,16 @@ func TestGetNewReviewComments(t *testing.T) { Type: SastComment, CommentInfo: vcsclient.PullRequestComment{ CommentInfo: vcsclient.CommentInfo{ - Content: outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent("High", "XSS Vulnerability", "", [][]formats.Location{}, writer), writer), + Content: outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(false, writer, formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "sast-rule", + }, + Finding: "XSS Vulnerability", + }), writer), }, PullRequestDiff: vcsclient.PullRequestDiff{ OriginalFilePath: "file1", diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index e392fa7b8..703318b02 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -133,10 +133,10 @@ func (ic *ScansIssuesCollection) HasErrors() bool { return false } -func (ic *ScansIssuesCollection) GetScanDetails(scanType utils.SubScanType, violation bool) map[severityutils.Severity]int { +func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubScanType, violation bool) map[severityutils.Severity]int { scanDetails := map[severityutils.Severity]int{} - if scanType == utils.ScaScan { + // Count Sca issues if violation { for _, violation := range ic.ScaViolations { scanDetails[severityutils.GetSeverity(violation.Severity)]++ @@ -151,33 +151,34 @@ func (ic *ScansIssuesCollection) GetScanDetails(scanType utils.SubScanType, viol } return scanDetails } - jasIssues := []formats.SourceCodeRow{} switch scanType { case utils.IacScan: + // Count Iac issues if violation { jasIssues = ic.IacViolations } else { jasIssues = ic.IacVulnerabilities } case utils.SecretsScan: + // Count Secrets issues if violation { jasIssues = ic.SecretsViolations } else { jasIssues = ic.SecretsVulnerabilities } case utils.SastScan: + // Count Sast issues if violation { jasIssues = ic.SastViolations } else { jasIssues = ic.SastVulnerabilities } } - + // Count the issues for _, issue := range jasIssues { scanDetails[severityutils.GetSeverity(issue.Severity)]++ } - return scanDetails } @@ -206,8 +207,8 @@ func (ic *ScansIssuesCollection) GetTotalIssues(includeSecrets bool) int { } type ApplicableEvidences struct { - Evidence formats.Evidence - Severity, FullDetails, IssueId, CveSummary, ImpactedDependency, Remediation string + Evidence formats.Evidence + Severity, ScannerDescription, IssueId, CveSummary, ImpactedDependency, Remediation string } func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []ApplicableEvidences) { @@ -215,10 +216,6 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl issueIdToIssue := map[string]formats.VulnerabilityOrViolationRow{} // Collect evidences from Violations for _, securityViolation := range ic.ScaViolations { - if securityViolation.Applicable != jasutils.Applicable.String() { - // We only want applicable issues - continue - } issueId := results.GetIssueIdentifier(securityViolation.Cves, securityViolation.IssueId, "-") if _, exists := issueIdToIssue[issueId]; exists { // No need to add the same issue twice @@ -226,6 +223,7 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl } for _, cve := range securityViolation.Cves { if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { + // We only want applicable issues issueIdToIssue[issueId] = securityViolation issueIdToApplicableInfo[issueId] = *cve.Applicability } @@ -233,10 +231,6 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl } // Collect evidences from Vulnerabilities for _, vulnerability := range ic.ScaVulnerabilities { - if vulnerability.Applicable != jasutils.Applicable.String() { - // We only want applicable issues - continue - } issueId := results.GetIssueIdentifier(vulnerability.Cves, vulnerability.IssueId, "-") if _, exists := issueIdToIssue[issueId]; exists { // No need to add the same issue twice @@ -244,6 +238,7 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl } for _, cve := range vulnerability.Cves { if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { + // We only want applicable issues issueIdToIssue[issueId] = vulnerability issueIdToApplicableInfo[issueId] = *cve.Applicability } @@ -261,7 +256,7 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl evidences = append(evidences, ApplicableEvidences{ Evidence: evidence, Severity: issue.Severity, - FullDetails: applicableInfo.ScannerDescription, + ScannerDescription: applicableInfo.ScannerDescription, IssueId: results.GetIssueIdentifier(issue.Cves, issue.IssueId, ","), CveSummary: issue.Summary, ImpactedDependency: fmt.Sprintf("%s:%s", issue.ImpactedDependencyName, issue.ImpactedDependencyVersion), diff --git a/utils/issues/issuescollection_test.go b/utils/issues/issuescollection_test.go index 4dc18b990..8c47aa38c 100644 --- a/utils/issues/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -3,15 +3,20 @@ package issues import ( "testing" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/stretchr/testify/assert" ) -func TestCountIssuesCollectionFindings(t *testing.T) { +func getTestData() ScansIssuesCollection { issuesCollection := ScansIssuesCollection{ ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ { ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + ImpactedDependencyName: "impacted-name", + ImpactedDependencyVersion: "1.0.0", + SeverityDetails: formats.SeverityDetails{Severity: "High"}, Components: []formats.ComponentRow{ { Name: "vuln-pack-name1", @@ -27,10 +32,29 @@ func TestCountIssuesCollectionFindings(t *testing.T) { }, }, }, - IssueId: "Xray-Id", + Cves: []formats.CveRow{{ + Id: "CVE-2021-1234", + Applicability: &formats.Applicability{ + Status: "Applicable", + ScannerDescription: "scanner", + Evidence: []formats.Evidence{ + {Reason: "reason", Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "snippet1"}}, + {Reason: "other reason", Location: formats.Location{File: "file2", StartLine: 5, StartColumn: 6, EndLine: 7, EndColumn: 8, Snippet: "snippet2"}}, + }, + }, + }}, + JfrogResearchInformation: &formats.JfrogResearchInformation{ + Remediation: "remediation", + }, + Summary: "summary", + Applicable: "Applicable", + IssueId: "Xray-Id", }, { ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + ImpactedDependencyName: "impacted-name2", + ImpactedDependencyVersion: "1.0.0", + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, Components: []formats.ComponentRow{ { Name: "vuln-pack-name3", @@ -38,39 +62,401 @@ func TestCountIssuesCollectionFindings(t *testing.T) { }, }, }, - IssueId: "Xray-Id2", + Cves: []formats.CveRow{{ + Id: "CVE-1111-2222", + Applicability: &formats.Applicability{Status: "Not Applicable"}, + }}, + Summary: "other summary", + Applicable: "Not Applicable", + IssueId: "Xray-Id2", }, }, - IacVulnerabilities: []formats.SourceCodeRow{ + ScaViolations: []formats.VulnerabilityOrViolationRow{ { - ScannerDescription: "Iac issue", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + ImpactedDependencyName: "impacted-name", + ImpactedDependencyVersion: "1.0.0", + SeverityDetails: formats.SeverityDetails{Severity: "Critical"}, + Components: []formats.ComponentRow{ + { + Name: "vuln-pack-name1", + Version: "1.0.0", + }, + }, + }, + Cves: []formats.CveRow{{ + Id: "CVE-2021-1234", + Applicability: &formats.Applicability{ + Status: "Applicable", + ScannerDescription: "scanner", + Evidence: []formats.Evidence{ + {Reason: "reason", Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "snippet1"}}, + }, + }, + }}, + JfrogResearchInformation: &formats.JfrogResearchInformation{ + Remediation: "remediation", + }, + Summary: "summary", + Applicable: "Applicable", + IssueId: "Xray-Id", + ViolationContext: formats.ViolationContext{ + Watch: "watch", + Policies: []string{"policy1", "policy2"}, + }, }, }, - SecretsVulnerabilities: []formats.SourceCodeRow{ - { - ScannerDescription: "Secret issue", + + LicensesViolations: []formats.LicenseViolationRow{{ + LicenseRow: formats.LicenseRow{ + LicenseKey: "license1", + LicenseName: "license-name1", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Components: []formats.ComponentRow{ + { + Name: "vuln-pack-name3", + Version: "1.0.0", + }, + }, + }, }, - }, - SecretsViolations: []formats.SourceCodeRow{ - { - ScannerDescription: "Secret violation", + ViolationContext: formats.ViolationContext{ + Watch: "lic-watch", + Policies: []string{"policy3"}, }, - }, + }}, + + IacVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "Low"}}}, + SecretsVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}, + SecretsViolations: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + ViolationContext: formats.ViolationContext{ + IssueId: "secret-violation-id", + Watch: "watch", + }, + }}, SastVulnerabilities: []formats.SourceCodeRow{ { - ScannerDescription: "Sast issue1", + SeverityDetails: formats.SeverityDetails{Severity: "Unknown"}, }, { - ScannerDescription: "Sast issue2", + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + }, + }, + } + return issuesCollection +} + +func TestCountIssuesCollectionFindings(t *testing.T) { + testCases := []struct { + name string + includeSecrets bool + expectedFindings int + }{ + { + name: "With Secrets", + includeSecrets: true, + expectedFindings: 9, + }, + { + name: "No Secrets", + includeSecrets: false, + expectedFindings: 7, + }, + } + issuesCollection := getTestData() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + findingsAmount := issuesCollection.GetTotalIssues(tc.includeSecrets) + assert.Equal(t, tc.expectedFindings, findingsAmount) + }) + } +} + +func TestGetTotalVulnerabilities(t *testing.T) { + testCases := []struct { + name string + includeSecrets bool + expectedFindings int + }{ + { + name: "With Secrets", + includeSecrets: true, + expectedFindings: 6, + }, + { + name: "No Secrets", + includeSecrets: false, + expectedFindings: 5, + }, + } + issuesCollection := getTestData() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + findingsAmount := issuesCollection.GetTotalVulnerabilities(tc.includeSecrets) + assert.Equal(t, tc.expectedFindings, findingsAmount) + }) + } +} + +func TestGetTotalViolations(t *testing.T) { + testCases := []struct { + name string + includeSecrets bool + expectedFindings int + }{ + { + name: "With Secrets", + includeSecrets: true, + expectedFindings: 3, + }, + { + name: "No Secrets", + includeSecrets: false, + expectedFindings: 2, + }, + } + issuesCollection := getTestData() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + findingsAmount := issuesCollection.GetTotalViolations(tc.includeSecrets) + assert.Equal(t, tc.expectedFindings, findingsAmount) + }) + } +} + +func TestGetScanIssuesSeverityCount(t *testing.T) { + testCases := []struct { + name string + scanType utils.SubScanType + violation bool + expectedSeverityCount map[string]int + }{ + { + name: "Sca Vulnerabilities", + scanType: utils.ScaScan, + violation: false, + expectedSeverityCount: map[string]int{"High": 1, "Low": 1}, + }, + { + name: "Sca Violations", + scanType: utils.ScaScan, + violation: true, + expectedSeverityCount: map[string]int{"Critical": 1, "Medium": 1}, + }, + { + name: "Iac Vulnerabilities", + scanType: utils.IacScan, + violation: false, + expectedSeverityCount: map[string]int{"Low": 1}, + }, + { + name: "Iac Violations", + scanType: utils.IacScan, + violation: true, + expectedSeverityCount: map[string]int{}, + }, + { + name: "Secrets Vulnerabilities", + scanType: utils.SecretsScan, + violation: false, + expectedSeverityCount: map[string]int{"High": 1}, + }, + { + name: "Secrets Violations", + scanType: utils.SecretsScan, + violation: true, + expectedSeverityCount: map[string]int{"High": 1}, + }, + { + name: "Sast Vulnerabilities", + scanType: utils.SastScan, + violation: false, + expectedSeverityCount: map[string]int{"High": 1, "Unknown": 1}, + }, + { + name: "Sast Violations", + scanType: utils.SastScan, + violation: true, + expectedSeverityCount: map[string]int{}, + }, + } + issuesCollection := getTestData() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + severityCount := issuesCollection.GetScanIssuesSeverityCount(tc.scanType, tc.violation) + assert.Len(t, severityCount, len(tc.expectedSeverityCount)) + for severity, count := range tc.expectedSeverityCount { + actualCount, ok := severityCount[severityutils.GetSeverity(severity)] + assert.True(t, ok) + assert.Equal(t, count, actualCount) + } + }) + } +} + +func TestGetApplicableEvidences(t *testing.T) { + testCases := []struct { + name string + issues ScansIssuesCollection + expectedEvidences []ApplicableEvidences + }{ + { + name: "No Issues", + }, + { + name: "With Issues", + issues: getTestData(), + expectedEvidences: []ApplicableEvidences{ + { + Evidence: formats.Evidence{Reason: "reason", Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "snippet1"}}, + Severity: "High", ScannerDescription: "scanner", IssueId: "Xray-Id", CveSummary: "summary", ImpactedDependency: "impacted-name:1.0.0", Remediation: "remediation", + }, + { + Evidence: formats.Evidence{Reason: "other reason", Location: formats.Location{File: "file2", StartLine: 5, StartColumn: 6, EndLine: 7, EndColumn: 8, Snippet: "snippet2"}}, + Severity: "High", ScannerDescription: "scanner", IssueId: "Xray-Id", CveSummary: "summary", ImpactedDependency: "impacted-name:1.0.0", Remediation: "remediation", + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.ElementsMatch(t, tc.expectedEvidences, tc.issues.GetApplicableEvidences()) + }) + } +} + +func TestIssuesExists(t *testing.T) { + testCases := []struct { + name string + issues ScansIssuesCollection + includeSecrets bool + expected bool + }{ + { + name: "No Issues", + }, + { + name: "With Issues", + issues: getTestData(), + expected: true, + }, + { + name: "With Secrets", + issues: getTestData(), + includeSecrets: true, + expected: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, tc.issues.IssuesExists(tc.includeSecrets)) + }) + } +} + +func TestHasErrors(t *testing.T) { + testCases := []struct { + name string + status formats.ScanStatus + expected bool + }{ + { + name: "Some Not Scanned", + status: formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(0), + SastStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(0), + }, + }, + { + name: "All Completed", + status: formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(0), + SastStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(0), + IacStatusCode: utils.NewIntPtr(0), + ApplicabilityStatusCode: utils.NewIntPtr(0), }, }, + { + name: "With Errors", + status: formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(-1), + SastStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(33), + IacStatusCode: utils.NewIntPtr(0), + ApplicabilityStatusCode: utils.NewIntPtr(0), + }, + expected: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + issues := ScansIssuesCollection{ScanStatus: tc.status} + assert.Equal(t, tc.expected, issues.HasErrors()) + }) } +} + +func TestIsScanNotCompleted(t *testing.T) { + issues := ScansIssuesCollection{ScanStatus: formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(-1), + SastStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(33), + }} + testCases := []struct { + name string + scan utils.SubScanType + expected bool + }{ + { + name: "Scanned and Passed", + scan: utils.SastScan, + }, + { + name: "Scanned and unknown Failed", + scan: utils.ScaScan, + expected: true, + }, + { + name: "Scanned and Failed", + scan: utils.SecretsScan, + expected: true, + }, + { + name: "Not Scanned", + scan: utils.IacScan, + expected: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, issues.IsScanNotCompleted(tc.scan)) + }) + } +} - // With Secrets - findingsAmount := issuesCollection.GetTotalIssues(true) - assert.Equal(t, 9, findingsAmount) - // No Secrets - findingsAmount = issuesCollection.GetTotalIssues(false) - assert.Equal(t, 7, findingsAmount) +func TestAppendStatus(t *testing.T) { + oldStatus := formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(-1), + SastStatusCode: utils.NewIntPtr(0), + } + newStatus := formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(0), + SastStatusCode: utils.NewIntPtr(33), + ApplicabilityStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(51), + } + expectedStatus := formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(-1), + SastStatusCode: utils.NewIntPtr(33), + ApplicabilityStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(51), + } + issues := ScansIssuesCollection{ScanStatus: oldStatus} + issues.AppendStatus(newStatus) + assert.Equal(t, expectedStatus, issues.ScanStatus) } diff --git a/utils/outputwriter/markdowntable.go b/utils/outputwriter/markdowntable.go index 32ca8eadc..b254f39bb 100644 --- a/utils/outputwriter/markdowntable.go +++ b/utils/outputwriter/markdowntable.go @@ -42,10 +42,13 @@ type MarkdownTableBuilder struct { type MarkdownColumnType string type MarkdownColumn struct { - Name string - Centered bool - ColumnType MarkdownColumnType - DefaultValue string + Name string + Centered bool + HideIfAllEmpty bool + ColumnType MarkdownColumnType + DefaultValue string + // Internal flag to determine if the column should be hidden + shouldHideColumn bool } // CellData represents the data of a cell in the markdown table. Each cell can contain multiple values. @@ -68,6 +71,15 @@ func NewMarkdownTable(columns ...string) *MarkdownTableBuilder { return NewMarkdownTableWithColumns(columnsInfo...) } +// Create a markdown table builder with the provided number of columns. +func NewNoHeaderMarkdownTable(nColumns int, firstColumnCentered bool) *MarkdownTableBuilder { + columnsInfo := []*MarkdownColumn{} + for i := 0; i < nColumns; i++ { + columnsInfo = append(columnsInfo, NewMarkdownTableSingleValueColumn("", cellDefaultValue, i != 0 || firstColumnCentered)) + } + return NewMarkdownTableWithColumns(columnsInfo...) +} + func NewMarkdownTableWithColumns(columnsInfo ...*MarkdownColumn) *MarkdownTableBuilder { return &MarkdownTableBuilder{columns: columnsInfo, delimiter: simpleSeparator} } @@ -76,16 +88,16 @@ func NewMarkdownTableSingleValueColumn(name, defaultValue string, centered bool) return &MarkdownColumn{Name: name, ColumnType: SeparatorDelimited, DefaultValue: defaultValue, Centered: centered} } -func NewMarkdownTableMultiValueColumn(name, defaultValue string, centered bool) *MarkdownColumn { - return &MarkdownColumn{Name: name, ColumnType: MultiRowColumn, DefaultValue: defaultValue, Centered: centered} -} - // Set the delimiter that will be used to separate multiple values in a cell. func (t *MarkdownTableBuilder) SetDelimiter(delimiter string) *MarkdownTableBuilder { t.delimiter = delimiter return t } +func (t *MarkdownTableBuilder) HasContent() bool { + return len(t.rows) > 0 +} + // Get the column information output controller by the provided name. func (t *MarkdownTableBuilder) GetColumnInfo(name string) *MarkdownColumn { for _, column := range t.columns { @@ -135,18 +147,38 @@ func (t *MarkdownTableBuilder) Build() string { return "" } var tableBuilder strings.Builder + // Calculate Hidden columns + for c := range t.columns { + // Reset shouldHideColumn flag + t.columns[c].shouldHideColumn = t.columns[c].HideIfAllEmpty + } + for _, row := range t.rows { + for c, cell := range row { + // In table, empty cell = cell with no values = cell with one empty value + t.columns[c].shouldHideColumn = t.columns[c].shouldHideColumn && (len(cell) == 0 || (len(cell) == 1 && cell[0] == "")) + } + } // Header - for c, column := range t.columns { - if c == 0 { + actualColumnCount := 0 + for _, column := range t.columns { + if column.shouldHideColumn { + continue + } + if actualColumnCount == 0 { tableBuilder.WriteString(fmt.Sprintf(firstCellPlaceholder, column.Name)) } else { tableBuilder.WriteString(fmt.Sprintf(cellPlaceholder, column.Name)) } + actualColumnCount++ } tableBuilder.WriteString("\n") // Separator - for c, column := range t.columns { - if c == 0 { + actualColumnCount = 0 + for _, column := range t.columns { + if column.shouldHideColumn { + continue + } + if actualColumnCount == 0 { columnSeparator := defaultFirstColumnSeparator if column.Centered { columnSeparator = centeredFirstColumnSeparator @@ -159,6 +191,7 @@ func (t *MarkdownTableBuilder) Build() string { } tableBuilder.WriteString(columnSeparator) } + actualColumnCount++ } // Content for _, row := range t.rows { @@ -193,6 +226,9 @@ func (t *MarkdownTableBuilder) getMultiValueRowsContent(row []CellData, multiVal } content := []string{} for column, cell := range row { + if t.columns[column].shouldHideColumn { + continue + } if column == multiValueColumnIndex { // Multi values column separated by different rows, add the specific value for this row content = append(content, value) @@ -215,6 +251,9 @@ func (t *MarkdownTableBuilder) getMultiValueRowsContent(row []CellData, multiVal func (t *MarkdownTableBuilder) getSeparatorDelimitedRowContent(row []CellData) string { content := []string{} for column, columnInfo := range t.columns { + if columnInfo.shouldHideColumn { + continue + } content = append(content, t.getCellContent(row[column], columnInfo.DefaultValue)) } return buildRowContent(content...) diff --git a/utils/outputwriter/markdowntable_test.go b/utils/outputwriter/markdowntable_test.go index 556b6e1d9..3d08263bc 100644 --- a/utils/outputwriter/markdowntable_test.go +++ b/utils/outputwriter/markdowntable_test.go @@ -97,7 +97,7 @@ func TestMarkdownTableBuild(t *testing.T) { name: "No rows", columns: []string{"col1"}, rows: [][]string{}, - expectedOutput: "| col1 |\n" + tableRowFirstColumnSeparator, + expectedOutput: "| col1 |\n" + defaultFirstColumnSeparator, }, { name: "Same number of columns", @@ -107,7 +107,7 @@ func TestMarkdownTableBuild(t *testing.T) { {"row2col1", "row2col2"}, {"row3col1", "row3col2"}, }, - expectedOutput: "| col1 | col2 |\n" + tableRowFirstColumnSeparator + tableRowColumnSeparator + ` + expectedOutput: "| col1 | col2 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + ` | row1col1 | row1col2 | | row2col1 | row2col2 | | row3col1 | row3col2 |`, @@ -123,7 +123,7 @@ func TestMarkdownTableBuild(t *testing.T) { {"row4col1"}, {"row5col1", "row5col2", "row5col3"}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + tableRowFirstColumnSeparator + tableRowColumnSeparator + tableRowColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` | row1col1 | row1col2 | - | | row2col1 | row2col2 | - | | row3col1 | - | row3col3 | @@ -143,6 +143,59 @@ func TestMarkdownTableBuild(t *testing.T) { } } +func TestHideEmptyColumnsInTable(t *testing.T) { + columns := []*MarkdownColumn{ + {Name: "col1", HideIfAllEmpty: true}, + {Name: "col2", HideIfAllEmpty: true, Centered: true}, + {Name: "col3", HideIfAllEmpty: false}, + {Name: "col4", HideIfAllEmpty: true}, + } + testCases := []struct { + name string + rows [][]string + expectedOutput string + }{ + { + name: "Defined as hidden but not empty", + rows: [][]string{ + {"row1col1", "row1col2", "", "row1col4"}, + {"row2col1", "row2col2", "", "row2col4"}, + }, + expectedOutput: "| col1 | col2 | col3 | col4 |\n" + defaultFirstColumnSeparator + centeredColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` +| row1col1 | row1col2 | - | row1col4 | +| row2col1 | row2col2 | - | row2col4 |`, + }, + { + name: "Defined as hidden and some empty", + rows: [][]string{ + {"row1col1", "", "row1col3", ""}, + {"row2col1", "", "", ""}, + }, + expectedOutput: "| col1 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + ` +| row1col1 | row1col3 | +| row2col1 | - |`, + }, + { + name: "Defined as hidden and all empty", + rows: [][]string{ + {"", "", "row1col3", ""}, + {"", "", "", ""}, + }, + expectedOutput: "| col3 |\n" + defaultFirstColumnSeparator + ` +| row1col3 |`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + table := NewMarkdownTableWithColumns(columns...) + for _, row := range tc.rows { + table.AddRow(row...) + } + assert.Equal(t, tc.expectedOutput, table.Build()) + }) + } +} + func TestMultipleValuesInColumnRow(t *testing.T) { testCases := []struct { name string @@ -156,7 +209,7 @@ func TestMultipleValuesInColumnRow(t *testing.T) { rows: [][]CellData{ {{""}, {"row1col2"}, {"row1col3"}}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + tableRowFirstColumnSeparator + tableRowColumnSeparator + tableRowColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` | - | row1col2 | row1col3 |`, }, { @@ -166,7 +219,7 @@ func TestMultipleValuesInColumnRow(t *testing.T) { {{"row1col1"}, {"row1col2"}, {"row1col3"}}, {{"row2col1"}, {"row2col2"}, {"row2col3"}}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + tableRowFirstColumnSeparator + tableRowColumnSeparator + tableRowColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` | row1col1 | row1col2 | row1col3 | | row2col1 | row2col2 | row2col3 |`, }, @@ -178,7 +231,7 @@ func TestMultipleValuesInColumnRow(t *testing.T) { {{"row2col1"}, {"row2col2"}, {"row2col3val1", "row2col3val2"}}, {{"row3col1"}, {"row3col2val1", "row3col2val2", "row3col2val3"}, {"row3col3"}}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + tableRowFirstColumnSeparator + tableRowColumnSeparator + tableRowColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` | row1col1 | - | row1col3 | | row2col1 | row2col2 | row2col3val1, row2col3val2 | | row3col1 | row3col2val1, row3col2val2, row3col2val3 | row3col3 |`, @@ -191,7 +244,7 @@ func TestMultipleValuesInColumnRow(t *testing.T) { {{"row2col1"}, {"row2col2"}, {"row2col3val1", "row2col3val2"}}, {{"row3col1"}, {"row3col2val1", "row3col2val2", "row3col2val3"}, {"row3col3"}}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + tableRowFirstColumnSeparator + tableRowColumnSeparator + tableRowColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` | row1col1 | - | row1col3 | | row2col1 | row2col2 | row2col3val1, row2col3val2 | | row3col1 | row3col2val1 | row3col3 | diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 1d9bd5e9c..4f683b3aa 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -162,7 +162,7 @@ func GetFrogbotErrorCommentContent(contentForComments []string, err error, write return ConvertContentToComments(contentForComments, writer, getFrogbotErrorSuffixDecorator(writer, err), GetFrogbotCommentBaseDecorator(writer)) } -// Adding markdown prefix to identify Frogbot comment and a footer with the link to the documentation +// Adding markdown suffix to show the error details and next steps func getFrogbotErrorSuffixDecorator(writer OutputWriter, err error) CommentDecorator { return func(_ int, content string) string { var comment strings.Builder @@ -179,8 +179,7 @@ func getFrogbotErrorSuffixDecorator(writer OutputWriter, err error) CommentDecor ) WriteNewLine(&comment) WriteContent(&comment, "Thank you for your understanding!") - - return content + writer.MarkAsDetails(jobErrorTitle, 3, comment.String()) + return content + "\n" + writer.MarkAsDetails(jobErrorTitle, 3, comment.String()) } } @@ -197,12 +196,14 @@ func ScanSummaryContent(issues issues.ScansIssuesCollection, violationContext st totalIssues = issues.GetTotalViolations(includeSecrets) violations = true } - title := MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", violationContext, totalIssues)) + // Title + WriteContent(&contentBuilder, writer.MarkAsTitle(scanSummaryTitle, 2)) if issues.HasErrors() { - title = MarkAsBullet(fmt.Sprintf("Frogbot attempted to scan for %s but encountered an error.", violationContext)) + WriteContent(&contentBuilder, MarkAsBullet(fmt.Sprintf("Frogbot attempted to scan for %s but encountered an error.", violationContext))) + return contentBuilder.String() + } else { + WriteContent(&contentBuilder, MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", violationContext, totalIssues))) } - // Title - WriteContent(&contentBuilder, writer.MarkAsTitle(scanSummaryTitle, 2), title) WriteNewLine(&contentBuilder) // Create table, a row for each sub scans summary secretsDetails := "" @@ -241,13 +242,13 @@ func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType var severityCountMap map[severityutils.Severity]int switch scanType { case utils.ScaScan: - severityCountMap = issues.GetScanDetails(utils.ScaScan, violation) + severityCountMap = issues.GetScanIssuesSeverityCount(utils.ScaScan, violation) case utils.SastScan: - severityCountMap = issues.GetScanDetails(utils.SastScan, violation) + severityCountMap = issues.GetScanIssuesSeverityCount(utils.SastScan, violation) case utils.SecretsScan: - severityCountMap = issues.GetScanDetails(utils.SecretsScan, violation) + severityCountMap = issues.GetScanIssuesSeverityCount(utils.SecretsScan, violation) case utils.IacScan: - severityCountMap = issues.GetScanDetails(utils.IacScan, violation) + severityCountMap = issues.GetScanIssuesSeverityCount(utils.IacScan, violation) } totalIssues := getTotalIssues(severityCountMap) if totalIssues == 0 { @@ -325,7 +326,7 @@ func getDecoratorWithSecurityViolationTitle(writer OutputWriter) func(int, strin func getSecurityViolationsSummaryTable(violations []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { // Construct table - columns := []string{"Severity/Risk", "ID"} + columns := []string{"Severity", "ID"} if writer.IsShowingCaColumn() { columns = append(columns, "Contextual Analysis") } @@ -401,7 +402,7 @@ func getLicenseViolationsDetailsContent(licenseViolations []formats.LicenseViola if len(licenseViolations) == 1 { content = append(content, getScaLicenseViolationDetails(violation, writer)) } else { - content = append(content, writer.MarkAsDetails( + content = append(content, "\n"+writer.MarkAsDetails( getComponentIssueIdentifier(violation.LicenseKey, violation.ImpactedDependencyName, violation.ImpactedDependencyVersion, violation.Watch), 4, getScaLicenseViolationDetails(violation, writer), )) @@ -417,14 +418,25 @@ func getLicenseViolationsDetailsContent(licenseViolations []formats.LicenseViola } func getScaLicenseViolationDetails(violation formats.LicenseViolationRow, writer OutputWriter) (content string) { - noHeaderTable := NewMarkdownTable("", "") - - if len(violation.Policies) > 0 { - noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(violation.Policies...)) + var contentBuilder strings.Builder + // Title + WriteNewLine(&contentBuilder) + WriteContent(&contentBuilder, writer.MarkAsTitle("Violation Details", 3)) + // Details Table + directComponent := []string{} + for _, component := range violation.ImpactedDependencyDetails.Components { + directComponent = append(directComponent, fmt.Sprintf("%s:%s", component.Name, component.Version)) } + noHeaderTable := NewNoHeaderMarkdownTable(2, false) + + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(violation.Policies...)) + noHeaderTable.AddRow(MarkAsBold("Watch Name:"), violation.Watch) + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Direct Dependencies:")), NewCellData(directComponent...)) + noHeaderTable.AddRow(MarkAsBold("Impacted Dependency:"), fmt.Sprintf("%s:%s", violation.ImpactedDependencyName, violation.ImpactedDependencyVersion)) noHeaderTable.AddRow(MarkAsBold("Full Name:"), violation.LicenseName) - return noHeaderTable.Build() + WriteContent(&contentBuilder, noHeaderTable.Build(), "\n") + return contentBuilder.String() } // Sca Vulnerabilities @@ -485,11 +497,11 @@ func ApplicableCveReviewContent(issue issues.ApplicableEvidences, writer OutputW WriteContent(&contentBuilder, writer.MarkAsTitle(contextualAnalysisTitle, 2), writer.MarkInCenter(GetApplicabilityDescriptionTable(issue.Severity, issue.IssueId, issue.ImpactedDependency, issue.Evidence.Reason, writer)), - writer.MarkAsDetails("Description", 3, issue.FullDetails), - writer.MarkAsDetails("CVE details", 3, issue.CveSummary), + writer.MarkAsDetails("Description", 3, "\n"+issue.ScannerDescription+"\n"), + writer.MarkAsDetails("CVE details", 3, "\n"+issue.CveSummary+"\n"), ) if len(issue.Remediation) > 0 { - WriteContent(&contentBuilder, writer.MarkAsDetails("Remediation", 3, issue.Remediation)) + WriteContent(&contentBuilder, writer.MarkAsDetails("Remediation", 3, "\n\n"+issue.Remediation+"\n\n")) } return contentBuilder.String() } @@ -501,138 +513,78 @@ func GetApplicabilityDescriptionTable(severity, issueId, impactedDependency, fin // JAS -func getJasIssueDescriptionTable(issue formats.SourceCodeRow, writer OutputWriter) string { - columns := []string{"Severity"} - rowData := []string{writer.FormattedSeverity(issue.Severity, "Applicable")} - // Optional issueId column (as stored at the platform) - if issue.IssueId != "" { - columns = append(columns, "ID") - rowData = append(rowData, issue.IssueId) - } - columns = append(columns, "Finding") - rowData = append(rowData, issue.Finding) - return NewMarkdownTable(columns...).AddRow(rowData...).Build() -} - -func getJasFullDescription(issue formats.SourceCodeRow, violation bool, issueDescTable string, writer OutputWriter) string { - var contentBuilder strings.Builder - // Separator - WriteNewLine(&contentBuilder) - // Write the vulnerability/violation details - WriteContent(&contentBuilder, writer.MarkAsTitle(fmt.Sprintf("%s Details", getIssueType(violation)), 3), issueDescTable) - // Separator - WriteNewLine(&contentBuilder) - // Write the description - WriteContent(&contentBuilder, issue.ScannerDescription) - return contentBuilder.String() -} - -func getBaseJasDetailsTable(watch string, policies, cwe []string, writer OutputWriter) *MarkdownTableBuilder { - noHeaderTable := NewMarkdownTable("", "").SetDelimiter(writer.Separator()) - // For Violations - if len(policies) > 0 { - noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(policies...)) - } - if watch != "" { - noHeaderTable.AddRow(MarkAsBold("Watch Name:"), watch) - } - // General CWE attribute if exists - if len(cwe) > 0 { - noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("CWE:")), NewCellData(cwe...)) - } - return noHeaderTable -} - -func IacReviewContent(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { +func IacReviewContent(violation bool, writer OutputWriter, issues ...formats.SourceCodeRow) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, writer.MarkAsTitle(fmt.Sprintf("%s %s", iacTitle, getIssueType(violation)), 2), - writer.MarkInCenter(getJasIssueDescriptionTable(issue, writer)), - writer.MarkAsDetails("Full description", 3, getIacFullDescription(issue, violation, writer)), + writer.MarkInCenter(getJasIssueDescriptionTable(writer, issues...)), + getJasFullDescription(violation, writer, getBaseJasDetailsTable, issues...), ) return contentBuilder.String() } -func getIacFullDescription(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { - return getJasFullDescription(issue, violation, getBaseJasDetailsTable(issue.Watch, issue.Policies, issue.CWE, writer).Build(), writer) -} - -func SastReviewContent(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { +func SastReviewContent(violation bool, writer OutputWriter, issues ...formats.SourceCodeRow) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, writer.MarkAsTitle(fmt.Sprintf("%s %s", sastTitle, getIssueType(violation)), 2), - writer.MarkInCenter(getJasIssueDescriptionTable(issue, writer)), - writer.MarkAsDetails("Full description", 3, getSastFullDescription(issue, violation, writer)), + writer.MarkInCenter(getJasIssueDescriptionTable(writer, issues...)), + getJasFullDescription(violation, writer, getSastRuleFullDescriptionTable, issues...), ) - if len(issue.CodeFlow) > 0 { - WriteContent(&contentBuilder, writer.MarkAsDetails("Code Flows", 3, sastCodeFlowsReviewContent(issue.CodeFlow, writer))) - } return contentBuilder.String() } -func getSastFullDescription(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { - table := getBaseJasDetailsTable(issue.Watch, issue.Policies, issue.CWE, writer) - table.AddRow(MarkAsBold("Rule ID:"), issue.RuleId) - return getJasFullDescription(issue, violation, table.Build(), writer) +func getSastRuleFullDescriptionTable(info formats.ScannerInfo, writer OutputWriter) *MarkdownTableBuilder { + table := getBaseJasDetailsTable(info, writer) + table.AddRow(MarkAsBold("Rule ID:"), info.RuleId) + return table } -func sastCodeFlowsReviewContent(codeFlows [][]formats.Location, writer OutputWriter) string { - var contentBuilder strings.Builder - for _, flow := range codeFlows { - WriteContent(&contentBuilder, writer.MarkAsDetails("Vulnerable data flow analysis result", 4, sastDataFlowLocationsReviewContent(flow))) - } - return contentBuilder.String() -} - -func sastDataFlowLocationsReviewContent(flow []formats.Location) string { - var contentBuilder strings.Builder - for _, location := range flow { - WriteContent(&contentBuilder, fmt.Sprintf("%s %s (at %s line %d)\n", "↘️", MarkAsQuote(location.Snippet), location.File, location.StartLine)) - } - return contentBuilder.String() -} - -func SecretReviewContent(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { - applicability := "" - if issue.Applicability != nil { - applicability = issue.Applicability.Status - } +func SecretReviewContent(violation bool, writer OutputWriter, issues ...formats.SourceCodeRow) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, writer.MarkAsTitle(fmt.Sprintf("%s %s", secretsTitle, getIssueType(violation)), 2), - writer.MarkInCenter(getSecretsDescriptionTable(issue.Severity, issue.IssueId, issue.Finding, applicability, writer)), - writer.MarkAsDetails("Full description", 3, getSecretsFullDescription(issue, violation, writer)), + writer.MarkInCenter(getSecretsDescriptionTable(writer, issues...)), + getJasFullDescription(violation, writer, getSecretsRuleFullDescriptionTable, issues...), ) return contentBuilder.String() } -func getSecretsDescriptionTable(severity, issueId, finding, status string, writer OutputWriter) string { - // Determine the issue applicable status - applicability := jasutils.Applicable.String() - if status == jasutils.Inactive.String() { - applicability = jasutils.NotApplicable.String() - } - columns := []string{"Severity"} - rowData := []string{writer.FormattedSeverity(severity, applicability)} - // Determine if issueId is provided - if issueId != "" { - columns = append(columns, "ID") - rowData = append(rowData, issueId) - } - columns = append(columns, "Finding") - rowData = append(rowData, finding) - // Determine if status is provided - if status != "" { - columns = append(columns, "Status") - rowData = append(rowData, status) +func getSecretsDescriptionTable(writer OutputWriter, issues ...formats.SourceCodeRow) string { + // Construct table + table := NewMarkdownTable("Severity", "ID", "Status", "Finding", "Watch Name", "Policies").SetDelimiter(writer.Separator()) + // Hide optional columns if all empty (violations/no status) + table.GetColumnInfo("ID").HideIfAllEmpty = true + table.GetColumnInfo("Status").HideIfAllEmpty = true + table.GetColumnInfo("Watch Name").HideIfAllEmpty = true + table.GetColumnInfo("Policies").HideIfAllEmpty = true + // Construct rows + for _, issue := range issues { + // Determine the issue applicable status + applicability := jasutils.Applicable.String() + status := "" + if issue.Applicability != nil && issue.Applicability.Status != "" { + status = issue.Applicability.Status + if status == jasutils.Inactive.String() { + // Update the applicability status to Not Applicable for Inactive + applicability = jasutils.NotApplicable.String() + } + } + table.AddRowWithCellData( + NewCellData(writer.FormattedSeverity(issue.Severity, applicability)), + NewCellData(issue.IssueId), + NewCellData(status), + NewCellData(issue.Finding), + NewCellData(issue.Watch), + NewCellData(issue.Policies...), + ) } - return NewMarkdownTable(columns...).AddRow(rowData...).Build() + return table.Build() } -func getSecretsFullDescription(issue formats.SourceCodeRow, violation bool, writer OutputWriter) string { - table := getBaseJasDetailsTable(issue.Watch, issue.Policies, issue.CWE, writer) - table.AddRow(MarkAsBold("Abbreviation:"), issue.RuleId) - return getJasFullDescription(issue, violation, table.Build(), writer) +func getSecretsRuleFullDescriptionTable(info formats.ScannerInfo, writer OutputWriter) *MarkdownTableBuilder { + table := getBaseJasDetailsTable(info, writer) + table.AddRow(MarkAsBold("Abbreviation:"), info.RuleId) + return table } // Utilities @@ -673,7 +625,7 @@ func getScaSecurityIssueDetailsContent(issues []formats.VulnerabilityOrViolation if len(issues) == 1 { content = append(content, getScaSecurityIssueDetails(issue, violations, writer)) } else { - content = append(content, writer.MarkAsDetails( + content = append(content, "\n"+writer.MarkAsDetails( getComponentIssueIdentifier(results.GetIssueIdentifier(issue.Cves, issue.IssueId, ", "), issue.ImpactedDependencyName, issue.ImpactedDependencyVersion, issue.Watch), 4, getScaSecurityIssueDetails(issue, violations, writer), )) @@ -719,7 +671,7 @@ func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, viola for _, component := range issue.ImpactedDependencyDetails.Components { directComponent = append(directComponent, fmt.Sprintf("%s:%s", component.Name, component.Version)) } - noHeaderTable := NewMarkdownTable("", "") + noHeaderTable := NewNoHeaderMarkdownTable(2, false) if len(issue.Policies) > 0 { noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(issue.Policies...)) } @@ -770,106 +722,116 @@ func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, viola WriteContent(&contentBuilder, MarkAsBold("Remediation:"), issue.JfrogResearchInformation.Remediation) } + return contentBuilder.String() + "\n" +} + +func getJasIssueDescriptionTable(writer OutputWriter, issues ...formats.SourceCodeRow) string { + // Construct table + table := NewMarkdownTable("Severity", "ID", "Finding", "Watch Name", "Policies").SetDelimiter(writer.Separator()) + // Hide optional columns if all empty (not violations) + table.GetColumnInfo("ID").HideIfAllEmpty = true + table.GetColumnInfo("Watch Name").HideIfAllEmpty = true + table.GetColumnInfo("Policies").HideIfAllEmpty = true + // Construct rows + for _, issue := range issues { + table.AddRowWithCellData( + NewCellData(writer.FormattedSeverity(issue.Severity, "Applicable")), + NewCellData(issue.IssueId), + NewCellData(issue.Finding), + NewCellData(issue.Watch), + NewCellData(issue.Policies...), + ) + } + return table.Build() +} + +// For Jas we show description for each unique rule +func getJasFullDescription(violations bool, writer OutputWriter, generateRuleTable func(formats.ScannerInfo, OutputWriter) *MarkdownTableBuilder, issues ...formats.SourceCodeRow) string { + // Group by scanner info + rulesInfo := map[string]formats.ScannerInfo{} + codeFlows := map[string][][]formats.Location{} + for _, issue := range issues { + if _, ok := rulesInfo[issue.RuleId]; ok { + codeFlows[issue.RuleId] = append(codeFlows[issue.RuleId], issue.CodeFlow...) + continue + } + rulesInfo[issue.RuleId] = issue.ScannerInfo + codeFlows[issue.RuleId] = issue.CodeFlow + } + // Write the details for each rule + var contentBuilder strings.Builder + for _, info := range rulesInfo { + var scannerCodeFlows [][]formats.Location + if v, ok := codeFlows[info.RuleId]; ok { + scannerCodeFlows = v + } + if len(rulesInfo) == 1 { + WriteContent(&contentBuilder, + writer.MarkAsDetails("Full description", 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer)), + codeFlowsReviewContent(scannerCodeFlows, writer), + ) + break + } + WriteContent(&contentBuilder, writer.MarkAsDetails(getJasDetailsIdentifier(info), 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer, scannerCodeFlows...))) + } return contentBuilder.String() } -// TODO: DELETE - -// func VulnerabilitiesContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { -// if len(vulnerabilities) == 0 { -// return []string{} -// } -// content = append(content, writer.MarkAsTitle(vulnerableDependenciesTitle, 2)) -// content = append(content, vulnerabilitiesSummaryContent(vulnerabilities, writer)) -// content = append(content, vulnerabilityDetailsContent(vulnerabilities, writer)...) -// return -// } - -// func vulnerabilitiesSummaryContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) string { -// var contentBuilder strings.Builder -// WriteContent(&contentBuilder, -// writer.MarkAsTitle("✍️ Summary", 3), -// writer.MarkInCenter(getVulnerabilitiesSummaryTable(vulnerabilities, writer)), -// ) -// return contentBuilder.String() -// } - -// type vulnerabilityOrViolationDetails struct { -// details string -// title string -// dependencyName string -// dependencyVersion string -// } - -// func vulnerabilityDetailsContent(vulnerabilities []formats.VulnerabilityOrViolationRow, writer OutputWriter) (content []string) { -// vulnerabilitiesWithDetails := getVulnerabilityWithDetails(vulnerabilities) -// if len(vulnerabilitiesWithDetails) == 0 { -// return -// } -// // Prepare content for each vulnerability details -// for i := range vulnerabilitiesWithDetails { -// if len(vulnerabilitiesWithDetails) == 1 { -// content = append(content, vulnerabilitiesWithDetails[i].details) -// } else { -// content = append(content, writer.MarkAsDetails( -// fmt.Sprintf(`%s %s %s`, vulnerabilitiesWithDetails[i].title, -// vulnerabilitiesWithDetails[i].dependencyName, -// vulnerabilitiesWithDetails[i].dependencyVersion), -// 4, vulnerabilitiesWithDetails[i].details, -// )) -// } -// } -// // Split content if it exceeds the size limit and decorate it with title -// return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { -// contentBuilder := strings.Builder{} -// WriteContent(&contentBuilder, writer.MarkAsTitle(jfrogResearchDetailsSubTitle, 3)) -// WriteContent(&contentBuilder, detailsContent) -// return contentBuilder.String() -// }) -// } - -// func getVulnerabilityWithDetails(vulnerabilities []formats.VulnerabilityOrViolationRow) (vulnerabilitiesWithDetails []vulnerabilityOrViolationDetails) { -// for i := range vulnerabilities { -// vulDescriptionContent := createVulnerabilityResearchDescription(&vulnerabilities[i]) -// if vulDescriptionContent == "" { -// // No content -// continue -// } -// vulnerabilitiesWithDetails = append(vulnerabilitiesWithDetails, vulnerabilityOrViolationDetails{ -// details: vulDescriptionContent, -// title: getScaCveIdentifier(vulnerabilities[i].Cves, vulnerabilities[i].IssueId), -// dependencyName: vulnerabilities[i].ImpactedDependencyName, -// dependencyVersion: vulnerabilities[i].ImpactedDependencyVersion, -// }) -// } -// return -// } - -// func getScaCveIdentifier(cveRows []formats.CveRow, xrayId string) string { -// identifier := results.GetIssueIdentifier(cveRows, xrayId, ", ") -// if identifier == "" { -// return "" -// } -// return fmt.Sprintf("[ %s ]", identifier) -// } - -// func createVulnerabilityResearchDescription(vulnerability *formats.VulnerabilityOrViolationRow) string { -// var descriptionBuilder strings.Builder -// vulnResearch := vulnerability.JfrogResearchInformation -// if vulnResearch == nil { -// vulnResearch = &formats.JfrogResearchInformation{Details: vulnerability.Summary} -// } else if vulnResearch.Details == "" { -// vulnResearch.Details = vulnerability.Summary -// } - -// if vulnResearch.Details != "" { -// WriteContent(&descriptionBuilder, MarkAsBold("Description:"), vulnResearch.Details) -// } -// if vulnResearch.Remediation != "" { -// if vulnResearch.Details != "" { -// WriteNewLine(&descriptionBuilder) -// } -// WriteContent(&descriptionBuilder, MarkAsBold("Remediation:"), vulnResearch.Remediation) -// } -// return descriptionBuilder.String() -// } +func getJasDetailsIdentifier(info formats.ScannerInfo) string { + id := info.RuleId + if info.ScannerShortDescription != "" { + id = info.ScannerShortDescription + } + return fmt.Sprintf("[ %s ]", id) +} + +func getJasRuleFullDescription(violation bool, scannerDescription string, issueDescTable *MarkdownTableBuilder, writer OutputWriter, codeFlows ...[]formats.Location) string { + var contentBuilder strings.Builder + // Separator + WriteNewLine(&contentBuilder) + // Write the vulnerability/violation details + WriteContent(&contentBuilder, writer.MarkAsTitle(fmt.Sprintf("%s Details", getIssueType(violation)), 3)) + if issueDescTable != nil && issueDescTable.HasContent() { + WriteContent(&contentBuilder, issueDescTable.Build()) + // Separator + WriteNewLine(&contentBuilder) + } + // Write the description + WriteContent(&contentBuilder, scannerDescription, "\n") + // Write the code flows if exists + if len(codeFlows) > 0 { + WriteContent(&contentBuilder, codeFlowsReviewContent(codeFlows, writer)) + } + return contentBuilder.String() +} + +func codeFlowsReviewContent(codeFlows [][]formats.Location, writer OutputWriter) string { + if len(codeFlows) == 0 { + return "" + } + var contentBuilder strings.Builder + for _, flow := range codeFlows { + WriteContent(&contentBuilder, writer.MarkAsDetails("Vulnerable data flow analysis result", 4, dataFlowLocationsReviewContent(flow))) + } + return writer.MarkAsDetails("Code Flows", 3, contentBuilder.String()) +} + +func dataFlowLocationsReviewContent(flow []formats.Location) string { + var contentBuilder strings.Builder + for i, location := range flow { + if i == 0 { + WriteNewLine(&contentBuilder) + } + WriteContent(&contentBuilder, fmt.Sprintf("%s %s (at %s line %d)\n", "↘️", MarkAsQuote(location.Snippet), location.File, location.StartLine)) + } + return contentBuilder.String() +} + +func getBaseJasDetailsTable(ruleInfo formats.ScannerInfo, writer OutputWriter) *MarkdownTableBuilder { + noHeaderTable := NewNoHeaderMarkdownTable(2, false).SetDelimiter(writer.Separator()) + // General CWE attribute if exists + if len(ruleInfo.Cwe) > 0 { + noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("CWE:")), NewCellData(ruleInfo.Cwe...)) + } + return noHeaderTable +} diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index 75d26c826..34b3976d7 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -6,11 +6,14 @@ import ( "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/froggit-go/vcsutils" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/stretchr/testify/assert" ) -func TestGetPRSummaryContent(t *testing.T) { +func TestGetMainCommentContent(t *testing.T) { testCases := []struct { name string cases []OutputTestCase @@ -18,7 +21,7 @@ func TestGetPRSummaryContent(t *testing.T) { isComment bool }{ { - name: "Summary comment No issues found", + name: "Main comment No issues found", issuesExists: false, isComment: true, cases: []OutputTestCase{ @@ -75,7 +78,7 @@ func TestGetPRSummaryContent(t *testing.T) { }, }, { - name: "Summary comment Found issues", + name: "Main comment Found issues", issuesExists: true, isComment: true, cases: []OutputTestCase{ @@ -193,16 +196,52 @@ func TestGetPRSummaryContent(t *testing.T) { } func TestScanSummaryContent(t *testing.T) { + testScanStatus := formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(0), + ApplicabilityStatusCode: utils.NewIntPtr(0), + SastStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(0), + } + testIssues := issues.ScansIssuesCollection{ + ScaVulnerabilities: []formats.VulnerabilityOrViolationRow{ + {ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "Critical"}}}, + {ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}, + {ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}, + {ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "Medium"}}}, + {ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "Low"}}}, + {ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "Unknown"}}}, + }, + ScaViolations: []formats.VulnerabilityOrViolationRow{ + {ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "Critical"}}}, + }, + LicensesViolations: []formats.LicenseViolationRow{ + {LicenseRow: formats.LicenseRow{ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}}, + {LicenseRow: formats.LicenseRow{ImpactedDependencyDetails: formats.ImpactedDependencyDetails{SeverityDetails: formats.SeverityDetails{Severity: "Medium"}}}}, + }, + SecretsVulnerabilities: []formats.SourceCodeRow{ + {SeverityDetails: formats.SeverityDetails{Severity: "High"}}, + {SeverityDetails: formats.SeverityDetails{Severity: "High"}}, + }, + SastVulnerabilities: []formats.SourceCodeRow{ + {SeverityDetails: formats.SeverityDetails{Severity: "High"}}, + {SeverityDetails: formats.SeverityDetails{Severity: "High"}}, + {SeverityDetails: formats.SeverityDetails{Severity: "Low"}}, + }, + SastViolations: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}, + } + testCases := []struct { name string violationContext string includeSecrets bool + scanStatus formats.ScanStatus issues issues.ScansIssuesCollection cases []OutputTestCase }{ { - name: "No issues", - issues: issues.ScansIssuesCollection{}, + name: "No issues", + issues: issues.ScansIssuesCollection{}, + scanStatus: testScanStatus, cases: []OutputTestCase{ { name: "Standard output", @@ -216,318 +255,436 @@ func TestScanSummaryContent(t *testing.T) { }, }, }, + { + name: "With vulnerabilities", + issues: testIssues, + scanStatus: testScanStatus, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_simplified.md")}, + }, + }, + }, + { + name: "With violation context", + issues: testIssues, + scanStatus: testScanStatus, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_violation_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_violation_simplified.md")}, + }, + }, + }, + { + name: "with errors", + issues: issues.ScansIssuesCollection{}, + scanStatus: formats.ScanStatus{ + IacStatusCode: utils.NewIntPtr(33), + }, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_error_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_error_simplified.md")}, + }, + }, + }, } for _, tc := range testCases { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) + tc.issues.ScanStatus = tc.scanStatus output := ScanSummaryContent(tc.issues, tc.violationContext, tc.includeSecrets, test.writer) - assert.Len(t, output, 1) - assert.Equal(t, expectedOutput, output[0]) + assert.Equal(t, expectedOutput, output) }) } } } -// func TestVulnerabilitiesContent(t *testing.T) { -// testCases := []struct { -// name string -// vulnerabilities []formats.VulnerabilityOrViolationRow -// cases []OutputTestCase -// }{ -// { -// name: "No vulnerabilities", -// vulnerabilities: []formats.VulnerabilityOrViolationRow{}, -// cases: []OutputTestCase{ -// { -// name: "Standard output", -// writer: &StandardOutput{}, -// expectedOutput: []string{""}, -// }, -// { -// name: "Simplified output", -// writer: &SimplifiedOutput{}, -// expectedOutput: []string{""}, -// }, -// }, -// }, -// { -// name: "One vulnerability", -// vulnerabilities: []formats.VulnerabilityOrViolationRow{ -// { -// Summary: "Summary CVE-2022-26652", -// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ -// SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, -// ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", -// ImpactedDependencyVersion: "v0.21.0", -// Components: []formats.ComponentRow{ -// { -// Name: "github.com/nats-io/nats-streaming-server", -// Version: "v0.21.0", -// }, -// }, -// }, -// Applicable: "Undetermined", -// FixedVersions: []string{"[0.24.3]"}, -// JfrogResearchInformation: &formats.JfrogResearchInformation{ -// Details: "Research CVE-2022-26652 details", -// Remediation: "some remediation", -// }, -// Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, -// }, -// }, -// cases: []OutputTestCase{ -// { -// name: "Standard output", -// writer: &StandardOutput{}, -// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_standard.md")}, -// }, -// { -// name: "Simplified output", -// writer: &SimplifiedOutput{}, -// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_simplified.md")}, -// }, -// }, -// }, -// { -// name: "One vulnerability, no Details", -// vulnerabilities: []formats.VulnerabilityOrViolationRow{ -// { -// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ -// SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, -// ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", -// ImpactedDependencyVersion: "v0.21.0", -// Components: []formats.ComponentRow{ -// { -// Name: "github.com/nats-io/nats-streaming-server", -// Version: "v0.21.0", -// }, -// }, -// }, -// Applicable: "Undetermined", -// FixedVersions: []string{"[0.24.3]"}, -// Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, -// }, -// }, -// cases: []OutputTestCase{ -// { -// name: "Standard output", -// writer: &StandardOutput{}, -// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_standard.md")}, -// }, -// { -// name: "Simplified output", -// writer: &SimplifiedOutput{}, -// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_simplified.md")}, -// }, -// }, -// }, -// { -// name: "multiple Vulnerabilities with Contextual Analysis", -// vulnerabilities: []formats.VulnerabilityOrViolationRow{ -// { -// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ -// SeverityDetails: severityutils.GetAsDetails(severityutils.Critical, jasutils.NotApplicable, false), -// ImpactedDependencyName: "impacted", -// ImpactedDependencyVersion: "3.0.0", -// Components: []formats.ComponentRow{ -// {Name: "dep1", Version: "1.0.0"}, -// {Name: "dep2", Version: "2.0.0"}, -// }, -// }, -// Applicable: "Not Applicable", -// FixedVersions: []string{"4.0.0", "5.0.0"}, -// Cves: []formats.CveRow{{Id: "CVE-1111-11111", Applicability: &formats.Applicability{Status: "Not Applicable"}}}, -// }, -// { -// Summary: "Summary XRAY-122345", -// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ -// SeverityDetails: severityutils.GetAsDetails(severityutils.High, jasutils.ApplicabilityUndetermined, false), -// ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", -// ImpactedDependencyVersion: "v0.21.0", -// Components: []formats.ComponentRow{ -// { -// Name: "github.com/nats-io/nats-streaming-server", -// Version: "v0.21.0", -// }, -// }, -// }, -// Applicable: "Undetermined", -// FixedVersions: []string{"[0.24.1]"}, -// IssueId: "XRAY-122345", -// JfrogResearchInformation: &formats.JfrogResearchInformation{ -// Remediation: "some remediation", -// }, -// Cves: []formats.CveRow{{}}, -// }, -// { -// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ -// SeverityDetails: severityutils.GetAsDetails(severityutils.Medium, jasutils.Applicable, false), -// ImpactedDependencyName: "component-D", -// ImpactedDependencyVersion: "v0.21.0", -// Components: []formats.ComponentRow{ -// { -// Name: "component-D", -// Version: "v0.21.0", -// }, -// }, -// }, -// Applicable: "Applicable", -// FixedVersions: []string{"[0.24.3]"}, -// JfrogResearchInformation: &formats.JfrogResearchInformation{ -// Remediation: "some remediation", -// }, -// Cves: []formats.CveRow{ -// {Id: "CVE-2022-26652"}, -// {Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable"}}, -// }, -// }, -// { -// Summary: "Summary", -// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ -// SeverityDetails: severityutils.GetAsDetails(severityutils.Low, jasutils.ApplicabilityUndetermined, false), -// ImpactedDependencyName: "github.com/mholt/archiver/v3", -// ImpactedDependencyVersion: "v3.5.1", -// Components: []formats.ComponentRow{ -// { -// Name: "github.com/mholt/archiver/v3", -// Version: "v3.5.1", -// }, -// }, -// }, -// Applicable: "Undetermined", -// Cves: []formats.CveRow{}, -// }, -// }, -// cases: []OutputTestCase{ -// { -// name: "Standard output", -// writer: &StandardOutput{MarkdownOutput{showCaColumn: true}}, -// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard.md")}, -// }, -// { -// name: "Simplified output", -// writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true}}, -// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified.md")}, -// }, -// { -// name: "Split Standard output", -// writer: &StandardOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1720, commentSizeLimit: 1720}}, -// expectedOutputPath: []string{ -// filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split1.md"), -// filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split2.md"), -// }, -// }, -// { -// name: "Split Simplified output", -// writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1000, commentSizeLimit: 1000}}, -// expectedOutputPath: []string{ -// filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split1.md"), -// filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split2.md"), -// }, -// }, -// }, -// }, -// { -// name: "Split vulnerabilities content", -// }, -// } -// for _, tc := range testCases { -// for _, test := range tc.cases { -// t.Run(tc.name+"_"+test.name, func(t *testing.T) { -// expectedOutput := GetExpectedTestCaseOutput(t, test) -// output := ConvertContentToComments(VulnerabilitiesContent(tc.vulnerabilities, test.writer), test.writer) -// assert.Len(t, output, len(expectedOutput)) -// assert.ElementsMatch(t, expectedOutput, output) -// }) -// } -// } -// } +func TestGetFrogbotErrorCommentContent(t *testing.T) { + +} + +func TestVulnerabilitiesContent(t *testing.T) { + testCases := []struct { + name string + vulnerabilities []formats.VulnerabilityOrViolationRow + cases []OutputTestCase + }{ + { + name: "No vulnerabilities", + vulnerabilities: []formats.VulnerabilityOrViolationRow{}, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutput: []string{""}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutput: []string{""}, + }, + }, + }, + { + name: "One vulnerability", + vulnerabilities: []formats.VulnerabilityOrViolationRow{ + { + Summary: "Summary CVE-2022-26652", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", + ImpactedDependencyVersion: "v0.21.0", + Components: []formats.ComponentRow{ + { + Name: "github.com/nats-io/nats-streaming-server", + Version: "v0.21.0", + }, + }, + }, + Applicable: "Undetermined", + FixedVersions: []string{"[0.24.3]"}, + JfrogResearchInformation: &formats.JfrogResearchInformation{ + Details: "Research CVE-2022-26652 details", + Remediation: "some remediation", + }, + Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, + }, + }, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_simplified.md")}, + }, + }, + }, + { + name: "One vulnerability, no Details", + vulnerabilities: []formats.VulnerabilityOrViolationRow{ + { + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", + ImpactedDependencyVersion: "v0.21.0", + Components: []formats.ComponentRow{ + { + Name: "github.com/nats-io/nats-streaming-server", + Version: "v0.21.0", + }, + }, + }, + Applicable: "Undetermined", + FixedVersions: []string{"[0.24.3]"}, + Cves: []formats.CveRow{{Id: "CVE-2022-26652"}}, + }, + }, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "one_vulnerability_no_details_simplified.md")}, + }, + }, + }, + { + name: "multiple Vulnerabilities with Contextual Analysis", + vulnerabilities: getTestScaIssues(false), + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{showCaColumn: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified.md")}, + }, + { + name: "Split Standard output", + writer: &StandardOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1720, commentSizeLimit: 1720}}, + expectedOutputPath: []string{ + filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split1.md"), + filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split2.md"), + }, + }, + { + name: "Split Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1000, commentSizeLimit: 1000}}, + expectedOutputPath: []string{ + filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split1.md"), + filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split2.md"), + }, + }, + }, + }, + } + for _, tc := range testCases { + for _, test := range tc.cases { + t.Run(tc.name+"_"+test.name, func(t *testing.T) { + expectedOutput := GetExpectedTestCaseOutput(t, test) + output := ConvertContentToComments(GetVulnerabilitiesContent(tc.vulnerabilities, test.writer), test.writer) + assert.Len(t, output, len(expectedOutput)) + assert.ElementsMatch(t, expectedOutput, output) + }) + } + } +} -// func TestLicensesContent(t *testing.T) { -// testCases := []struct { -// name string -// licenses []formats.LicenseViolationRow -// cases []OutputTestCase -// }{ -// { -// name: "No license violations", -// licenses: []formats.LicenseViolationRow{}, -// cases: []OutputTestCase{ -// { -// name: "Standard output", -// writer: &StandardOutput{}, -// expectedOutput: []string{""}, -// }, -// { -// name: "Simplified output", -// writer: &SimplifiedOutput{}, -// expectedOutput: []string{""}, -// }, -// }, -// }, -// { -// name: "License violations", -// licenses: []formats.LicenseViolationRow{ -// { -// LicenseRow: formats.LicenseRow{ -// LicenseKey: "License1", -// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ -// Components: []formats.ComponentRow{{Name: "Comp1", Version: "1.0"}}, -// ImpactedDependencyName: "Dep1", -// ImpactedDependencyVersion: "2.0", -// SeverityDetails: formats.SeverityDetails{ -// Severity: "High", -// }, -// }, -// }, -// }, -// { -// LicenseRow: formats.LicenseRow{ -// LicenseKey: "License2", -// ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ -// Components: []formats.ComponentRow{ -// { -// Name: "root", -// Version: "1.0.0", -// }, -// { -// Name: "minimatch", -// Version: "1.2.3", -// }, -// }, -// ImpactedDependencyName: "Dep2", -// ImpactedDependencyVersion: "3.0", -// SeverityDetails: formats.SeverityDetails{ -// Severity: "High", -// }, -// }, -// }, -// }, -// }, -// cases: []OutputTestCase{ -// { -// name: "Standard output", -// writer: &StandardOutput{}, -// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "license", "license_violation_standard.md")}, -// }, -// { -// name: "Simplified output", -// writer: &SimplifiedOutput{}, -// expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "license", "license_violation_simplified.md")}, -// }, -// }, -// }, -// } -// for _, tc := range testCases { -// for _, test := range tc.cases { -// t.Run(tc.name+"_"+test.name, func(t *testing.T) { -// assert.Equal(t, GetExpectedTestOutput(t, test), LicensesContent(tc.licenses, test.writer)) -// }) -// } -// } -// } +func TestSecurityViolationsContent(t *testing.T) { + testCases := []struct { + name string + issues issues.ScansIssuesCollection + cases []OutputTestCase + }{ + { + name: "No security violations", + issues: issues.ScansIssuesCollection{}, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutput: []string{""}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutput: []string{""}, + }, + }, + }, + { + name: "Security violations", + issues: issues.ScansIssuesCollection{ScaViolations: getTestScaIssues(true)}, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{showCaColumn: true, hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "violations", "security", "security_violation_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true, hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "violations", "security", "security_violation_simplified.md")}, + }, + }, + }, + } + for _, tc := range testCases { + for _, test := range tc.cases { + t.Run(tc.name+"_"+test.name, func(t *testing.T) { + expectedOutput := GetExpectedTestCaseOutput(t, test) + output := ConvertContentToComments(getSecurityViolationsContent(tc.issues, test.writer), test.writer) + assert.Len(t, output, len(expectedOutput)) + assert.ElementsMatch(t, expectedOutput, output) + }) + } + } +} + +func getTestScaIssues(violations bool) []formats.VulnerabilityOrViolationRow { + issues := []formats.VulnerabilityOrViolationRow{ + { + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: severityutils.GetAsDetails(severityutils.Critical, jasutils.NotApplicable, false), + ImpactedDependencyName: "impacted", + ImpactedDependencyVersion: "3.0.0", + Components: []formats.ComponentRow{ + {Name: "dep1", Version: "1.0.0"}, + {Name: "dep2", Version: "2.0.0"}, + }, + }, + Applicable: "Not Applicable", + FixedVersions: []string{"4.0.0", "5.0.0"}, + Cves: []formats.CveRow{{Id: "CVE-1111-11111", Applicability: &formats.Applicability{Status: "Not Applicable"}}}, + }, + { + Summary: "Summary XRAY-122345", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: severityutils.GetAsDetails(severityutils.High, jasutils.ApplicabilityUndetermined, false), + ImpactedDependencyName: "github.com/nats-io/nats-streaming-server", + ImpactedDependencyVersion: "v0.21.0", + Components: []formats.ComponentRow{ + { + Name: "github.com/nats-io/nats-streaming-server", + Version: "v0.21.0", + }, + }, + }, + Applicable: "Undetermined", + FixedVersions: []string{"[0.24.1]"}, + IssueId: "XRAY-122345", + JfrogResearchInformation: &formats.JfrogResearchInformation{ + Remediation: "some remediation", + }, + Cves: []formats.CveRow{{}}, + }, + { + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: severityutils.GetAsDetails(severityutils.Medium, jasutils.Applicable, false), + ImpactedDependencyName: "component-D", + ImpactedDependencyVersion: "v0.21.0", + Components: []formats.ComponentRow{ + { + Name: "component-D", + Version: "v0.21.0", + }, + }, + }, + Applicable: "Applicable", + FixedVersions: []string{"[0.24.3]"}, + JfrogResearchInformation: &formats.JfrogResearchInformation{ + Remediation: "some remediation", + }, + Cves: []formats.CveRow{ + {Id: "CVE-2022-26652"}, + {Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable"}}, + }, + }, + { + Summary: "Summary", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: severityutils.GetAsDetails(severityutils.Low, jasutils.ApplicabilityUndetermined, false), + ImpactedDependencyName: "github.com/mholt/archiver/v3", + ImpactedDependencyVersion: "v3.5.1", + Components: []formats.ComponentRow{ + { + Name: "github.com/mholt/archiver/v3", + Version: "v3.5.1", + }, + }, + }, + Applicable: "Undetermined", + Cves: []formats.CveRow{}, + }, + } + if violations { + for _, issue := range issues { + issue.ViolationContext = formats.ViolationContext{ + Watch: "watch", + Policies: []string{"policy1", "policy2"}, + } + } + } + return issues +} + +func TestLicensesContent(t *testing.T) { + testCases := []struct { + name string + licenses []formats.LicenseViolationRow + cases []OutputTestCase + }{ + { + name: "No license violations", + licenses: []formats.LicenseViolationRow{}, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{}, + expectedOutput: []string{""}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{}, + expectedOutput: []string{""}, + }, + }, + }, + { + name: "License violations", + licenses: []formats.LicenseViolationRow{ + { + LicenseRow: formats.LicenseRow{ + LicenseKey: "License1", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + Components: []formats.ComponentRow{{Name: "Comp1", Version: "1.0"}}, + ImpactedDependencyName: "Dep1", + ImpactedDependencyVersion: "2.0", + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + }, + }, + }, + }, + { + LicenseRow: formats.LicenseRow{ + LicenseKey: "License2", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + Components: []formats.ComponentRow{ + { + Name: "root", + Version: "1.0.0", + }, + { + Name: "minimatch", + Version: "1.2.3", + }, + }, + ImpactedDependencyName: "Dep2", + ImpactedDependencyVersion: "3.0", + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + }, + }, + }, + }, + }, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "violations", "license", "license_violation_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "violations", "license", "license_violation_simplified.md")}, + }, + }, + }, + } + for _, tc := range testCases { + for _, test := range tc.cases { + t.Run(tc.name+"_"+test.name, func(t *testing.T) { + assert.Equal(t, GetExpectedTestOutput(t, test), getLicenseViolationsContent(issues.ScansIssuesCollection{LicensesViolations: tc.licenses}, test.writer)) + }) + } + } +} func TestIsFrogbotReviewComment(t *testing.T) { testCases := []struct { @@ -625,36 +782,41 @@ func TestApplicableReviewContent(t *testing.T) { { name: "Applicable CVE review comment content", issue: issues.ApplicableEvidences{ - Severity: "Critical", - }, - severity: "Critical", - finding: "The vulnerable function flask.Flask.run is called", - fullDetails: "The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`.", - cve: "CVE-2022-29361", - cveDetails: "cveDetails", - impactedDependency: "werkzeug:1.0.1", - remediation: "some remediation", + Severity: "Critical", + IssueId: "CVE-2022-29361", + ScannerDescription: "The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`.", + CveSummary: "cveDetails", + ImpactedDependency: "werkzeug:1.0.1", + Remediation: "some remediation", + Evidence: formats.Evidence{ + Reason: "The vulnerable function flask.Flask.run is called", + }, + }, cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{}, + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "applicable", "applicable_review_content_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{}, + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "applicable", "applicable_review_content_simplified.md")}, }, }, }, { - name: "No remediation", - severity: "Critical", - finding: "The vulnerable function flask.Flask.run is called", - fullDetails: "The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`.", - cve: "CVE-2022-29361", - cveDetails: "cveDetails", - impactedDependency: "werkzeug:1.0.1", + name: "No remediation and internet connection", + issue: issues.ApplicableEvidences{ + Severity: "Critical", + IssueId: "CVE-2022-29361", + ScannerDescription: "The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`.", + CveSummary: "cveDetails", + ImpactedDependency: "werkzeug:1.0.1", + Evidence: formats.Evidence{ + Reason: "The vulnerable function flask.Flask.run is called", + }, + }, cases: []OutputTestCase{ { name: "Standard output", @@ -674,7 +836,7 @@ func TestApplicableReviewContent(t *testing.T) { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - assert.Equal(t, expectedOutput, ApplicableCveReviewContent(tc.severity, tc.finding, tc.fullDetails, tc.cve, tc.cveDetails, tc.impactedDependency, tc.remediation, test.writer)) + assert.Equal(t, expectedOutput, ApplicableCveReviewContent(tc.issue, test.writer)) }) } } @@ -682,77 +844,106 @@ func TestApplicableReviewContent(t *testing.T) { func TestSecretsReviewContent(t *testing.T) { testCases := []struct { - name string - issue formats.SourceCodeRow - violations bool - cases []OutputTestCase + name string + issues []formats.SourceCodeRow + cases []OutputTestCase }{ { name: "Secret review comment content", - issue: formats.SourceCodeRow{ - SeverityDetails: formats.SeverityDetails{Severity: "High"}, - IssueId: "secret-scanner-id", - Finding: "Secret keys were found", - ScannerDescription: "Scanner Description....", - }, + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Finding: "Secret keys were found", + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule-id", + Cwe: []string{"CWE-798", "CWE-799"}, + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Scanner Short Description", + }, + }}, cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{}, + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_review_content_no_ca_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{}, + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_review_content_no_ca_simplified.md")}, }, }, }, { name: "Secret review comment content with applicability status", - issue: formats.SourceCodeRow{ - SeverityDetails: formats.SeverityDetails{Severity: "High"}, - Applicability: &formats.Applicability{Status: "Active"}, - IssueId: "secret-scanner-id", - Finding: "Secret keys were found", - ScannerDescription: "Scanner Description....", - }, + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Applicability: &formats.Applicability{Status: jasutils.Active.String()}, + Finding: "Secret keys were found", + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule-id", + Cwe: []string{"CWE-798", "CWE-799"}, + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Scanner Short Description", + }, + }}, cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{}, + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_review_content_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{}, + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_review_content_simplified.md")}, }, }, }, { - name: "Secret violation review comment content with applicability status", - violations: true, - issue: formats.SourceCodeRow{ - SeverityDetails: formats.SeverityDetails{Severity: "High"}, - Applicability: &formats.Applicability{Status: "Active"}, - IssueId: "secret-scanner-id", - Finding: "Secret keys were found", - ScannerDescription: "Scanner Description....", - ViolationContext: formats.ViolationContext{ - Watch: "jas-watch", - Policies: []string{"policy1"}, + name: "Secrets violation review comment content with applicability status", + issues: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Applicability: &formats.Applicability{Status: jasutils.Active.String()}, + Finding: "Secret keys were found", + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule-id", + Cwe: []string{"CWE-798", "CWE-799"}, + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Scanner Short Description", + }, + ViolationContext: formats.ViolationContext{ + Watch: "jas-watch", + IssueId: "secret-violation-id", + Policies: []string{"policy1"}, + }, + }, + { + SeverityDetails: formats.SeverityDetails{Severity: "Critical"}, + Applicability: &formats.Applicability{Status: jasutils.Inactive.String()}, + Finding: "Secret keys were found", + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule-id", + Cwe: []string{"CWE-798", "CWE-799"}, + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Scanner Short Description", + }, + ViolationContext: formats.ViolationContext{ + Watch: "jas-watch2", + IssueId: "secret-violation-id-2", + Policies: []string{"policy1", "policy2"}, + }, }, }, cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{}, + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_violation_review_content_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{}, + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "secrets", "secret_violation_review_content_simplified.md")}, }, }, @@ -763,7 +954,14 @@ func TestSecretsReviewContent(t *testing.T) { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - assert.Equal(t, expectedOutput, SecretReviewContent(tc.issue, tc.violations, test.writer)) + violations := false + for _, issue := range tc.issues { + if issue.Watch != "" { + violations = true + break + } + } + assert.Equal(t, expectedOutput, SecretReviewContent(violations, test.writer, tc.issues...)) }) } } @@ -771,53 +969,59 @@ func TestSecretsReviewContent(t *testing.T) { func TestIacReviewContent(t *testing.T) { testCases := []struct { - name string - violations bool - issue formats.SourceCodeRow - cases []OutputTestCase + name string + issues []formats.SourceCodeRow + cases []OutputTestCase }{ { name: "Iac review comment content", - issue: formats.SourceCodeRow{ - SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, - Finding: "Missing auto upgrade was detected", - ScannerDescription: "Scanner Description....", - }, + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Finding: "Missing auto upgrade was detected", + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule-id", + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Scanner Short Description", + }, + }}, cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{}, + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "iac", "iac_review_content_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{}, + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "iac", "iac_review_content_simplified.md")}, }, }, }, { - name: "Iac violation review comment content", - violations: true, - issue: formats.SourceCodeRow{ - SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, - IssueId: "iac-scanner-id", - Finding: "Missing auto upgrade was detected", - ScannerDescription: "Scanner Description....", + name: "Iac violation review comment content", + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Finding: "Missing auto upgrade was detected", + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule-id", + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Scanner Short Description", + }, ViolationContext: formats.ViolationContext{ + IssueId: "iac-violation-id", Watch: "jas-watch", Policies: []string{"policy1", "policy2"}, }, - }, + }}, cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{}, + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "iac", "iac_violation_review_content_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{}, + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "iac", "iac_violation_review_content_simplified.md")}, }, }, @@ -828,7 +1032,14 @@ func TestIacReviewContent(t *testing.T) { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - assert.Equal(t, expectedOutput, IacReviewContent(tc.issue, tc.violations, test.writer)) + violations := false + for _, issue := range tc.issues { + if issue.Watch != "" { + violations = true + break + } + } + assert.Equal(t, expectedOutput, IacReviewContent(violations, test.writer, tc.issues...)) }) } } @@ -836,18 +1047,22 @@ func TestIacReviewContent(t *testing.T) { func TestSastReviewContent(t *testing.T) { testCases := []struct { - name string - violations bool - issue formats.SourceCodeRow - cases []OutputTestCase + name string + issues []formats.SourceCodeRow + cases []OutputTestCase }{ { - name: "No code flows", - issue: formats.SourceCodeRow{ - SeverityDetails: formats.SeverityDetails{Severity: "Low"}, - Finding: "Stack Trace Exposure", - ScannerDescription: "Scanner Description....", - }, + name: "No code flows (no internet connection)", + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + Finding: "Found a Use of Insecure Random", + ScannerInfo: formats.ScannerInfo{ + RuleId: "js-insecure-random", + Cwe: []string{"CWE-798", "CWE-799"}, + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Use of Insecure Random", + }, + }}, cases: []OutputTestCase{ { name: "Standard output", @@ -863,10 +1078,15 @@ func TestSastReviewContent(t *testing.T) { }, { name: "Sast review comment content", - issue: formats.SourceCodeRow{ - SeverityDetails: formats.SeverityDetails{Severity: "Low"}, - Finding: "Stack Trace Exposure", - ScannerDescription: "Scanner Description....", + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + Finding: "Found a Use of Insecure Random", + ScannerInfo: formats.ScannerInfo{ + RuleId: "js-insecure-random", + Cwe: []string{"CWE-798", "CWE-799"}, + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Use of Insecure Random", + }, CodeFlow: [][]formats.Location{ { { @@ -905,79 +1125,115 @@ func TestSastReviewContent(t *testing.T) { }, }, }, - }, + }}, cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{}, + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_review_content_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{}, + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_review_content_simplified.md")}, }, }, }, { - name: "Sast violation review comment content", - violations: true, - issue: formats.SourceCodeRow{ - SeverityDetails: formats.SeverityDetails{Severity: "Low"}, - Finding: "Stack Trace Exposure", - ScannerDescription: "Scanner Description....", - ViolationContext: formats.ViolationContext{ - Watch: "jas-watch", - Policies: []string{"policy1", "policy2"}, - }, - CodeFlow: [][]formats.Location{ - { - { - File: "file2", - StartLine: 1, - StartColumn: 2, - EndLine: 3, - EndColumn: 4, - Snippet: "other-snippet", - }, - { - File: "file", - StartLine: 0, - StartColumn: 0, - EndLine: 0, - EndColumn: 0, - Snippet: "snippet", - }, + name: "Sast violation review comment content", + issues: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + Finding: "Found a Use of Insecure Random", + ScannerInfo: formats.ScannerInfo{ + RuleId: "js-insecure-random", + Cwe: []string{"CWE-798", "CWE-799"}, + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Use of Insecure Random", }, - { + ViolationContext: formats.ViolationContext{ + IssueId: "sast-violation-id", + Watch: "jas-watch", + Policies: []string{"policy1", "policy2"}, + }, + CodeFlow: [][]formats.Location{ { - File: "file", - StartLine: 10, - StartColumn: 20, - EndLine: 10, - EndColumn: 30, - Snippet: "a-snippet", + { + File: "file2", + StartLine: 1, + StartColumn: 2, + EndLine: 3, + EndColumn: 4, + Snippet: "other-snippet", + }, + { + File: "file", + StartLine: 0, + StartColumn: 0, + EndLine: 0, + EndColumn: 0, + Snippet: "snippet", + }, }, { - File: "file", - StartLine: 0, - StartColumn: 0, - EndLine: 0, - EndColumn: 0, - Snippet: "snippet", + { + File: "file", + StartLine: 10, + StartColumn: 20, + EndLine: 10, + EndColumn: 30, + Snippet: "a-snippet", + }, + { + File: "file", + StartLine: 0, + StartColumn: 0, + EndLine: 0, + EndColumn: 0, + Snippet: "snippet", + }, }, }, }, + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Finding: "Found a Use of Insecure Random", + ScannerInfo: formats.ScannerInfo{ + RuleId: "js-insecure-random", + Cwe: []string{"CWE-798", "CWE-799"}, + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Use of Insecure Random", + }, + ViolationContext: formats.ViolationContext{ + IssueId: "sast-violation-id-2", + Watch: "jas-watch2", + Policies: []string{"policy3"}, + }, + }, + { + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Finding: "Found An Express Not Using Helmet", + ScannerInfo: formats.ScannerInfo{ + RuleId: "js-express-without-helmet", + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Express Not Using Helmet", + }, + ViolationContext: formats.ViolationContext{ + IssueId: "sast-violation-id-3", + Watch: "jas-watch2", + Policies: []string{"policy3"}, + }, + }, }, cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{}, + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_violation_review_content_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{}, + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "sast", "sast_violation_review_content_simplified.md")}, }, }, @@ -988,7 +1244,14 @@ func TestSastReviewContent(t *testing.T) { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - assert.Equal(t, expectedOutput, SastReviewContent(tc.issue, tc.violations, test.writer)) + violations := false + for _, issue := range tc.issues { + if issue.Watch != "" { + violations = true + break + } + } + assert.Equal(t, expectedOutput, SastReviewContent(violations, test.writer, tc.issues...)) }) } } diff --git a/utils/outputwriter/simplifiedoutput.go b/utils/outputwriter/simplifiedoutput.go index 1a4cb835a..3f5ff524e 100644 --- a/utils/outputwriter/simplifiedoutput.go +++ b/utils/outputwriter/simplifiedoutput.go @@ -36,7 +36,11 @@ func (smo *SimplifiedOutput) MarkInCenter(content string) string { } func (smo *SimplifiedOutput) MarkAsDetails(summary string, subTitleDepth int, content string) string { - return fmt.Sprintf("%s\n%s", smo.MarkAsTitle(summary, subTitleDepth), content) + delimiter := "\n" + if subTitleDepth == 0 { + delimiter = ": " + } + return fmt.Sprintf("%s%s%s", smo.MarkAsTitle(summary, subTitleDepth), delimiter, content) } func (smo *SimplifiedOutput) MarkAsTitle(title string, subTitleDepth int) string { diff --git a/utils/scandetails.go b/utils/scandetails.go index cad4f490d..19e5a3d24 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -204,8 +204,8 @@ func (sc *ScanDetails) RunInstallAndAudit(workDirs ...string) (auditResults *res SetAllowPartialResults(sc.allowPartialResults). SetExclusions(sc.PathExclusions). SetIsRecursiveScan(sc.IsRecursiveScan). - SetUseJas(!sc.DisableJas()) //. - // SetScansToPerform([]utils.SubScanType{utils.ScaScan, utils.ContextualAnalysisScan, utils.SastScan, utils.SecretsScan, utils.SecretTokenValidationScan}) + SetUseJas(!sc.DisableJas()). + SetScansToPerform([]utils.SubScanType{utils.ScaScan, utils.ContextualAnalysisScan, utils.SastScan, utils.SecretsScan, utils.SecretTokenValidationScan}) auditParams := audit.NewAuditParams(). SetWorkingDirs(workDirs). From 008930c37bc2f216e450dfba0c4c3f0b5e350b30 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 10 Dec 2024 16:39:35 +0200 Subject: [PATCH 17/34] fix content tests --- ...eview_content_no_remediation_simplified.md | 9 +- ..._review_content_no_remediation_standard.md | 21 +--- .../applicable_review_content_simplified.md | 13 ++- .../applicable_review_content_standard.md | 28 ++--- .../iac/iac_review_content_simplified.md | 21 ++-- .../iac/iac_review_content_standard.md | 20 +--- ...iac_violation_review_content_simplified.md | 29 ++--- .../iac_violation_review_content_standard.md | 31 ++--- .../sast/sast_review_content_standard.md | 2 +- .../sast_violation_review_content_standard.md | 6 +- .../secret_review_content_no_ca_simplified.md | 35 ++---- .../secret_review_content_no_ca_standard.md | 44 ++----- .../secret_review_content_simplified.md | 37 ++---- .../secrets/secret_review_content_standard.md | 46 ++------ ...ret_violation_review_content_simplified.md | 40 +++---- ...ecret_violation_review_content_standard.md | 57 +++------- .../structure/error_simplified.md | 39 +++++++ .../structure/error_standard.md | 26 +++++ .../structure/fix_mr_not_entitled.md | 9 +- .../structure/fix_pr_not_entitled.md | 9 +- .../structure/fix_simplified_not_entitled.md | 6 +- .../summary_comment_issues_mr_not_entitled.md | 9 +- .../summary_comment_issues_pr_not_entitled.md | 9 +- ...mment_issues_pr_not_entitled_with_title.md | 9 +- ..._comment_issues_simplified_not_entitled.md | 6 +- ...mmary_comment_no_issues_mr_not_entitled.md | 9 +- ...mmary_comment_no_issues_pr_not_entitled.md | 9 +- ...nt_no_issues_pr_not_entitled_with_title.md | 9 +- ...mment_no_issues_simplified_not_entitled.md | 6 +- ...sues_simplified_not_entitled_with_title.md | 6 +- .../summary/summary_error_simplified.md | 6 + .../summary/summary_error_standard.md | 2 + .../summary/summary_simplified.md | 25 ++-- .../summary/summary_standard.md | 19 ++-- .../summary/summary_violation_standard.md | 19 ++-- .../license/license_violation_simplified.md | 69 ++++++++++- .../license/license_violation_standard.md | 46 +++++--- .../security/security_violation_simplified.md | 107 +++++++++++++++--- .../security/security_violation_standard.md | 88 +++++++------- ...one_vulnerability_no_details_simplified.md | 10 +- .../one_vulnerability_no_details_standard.md | 8 +- .../one_vulnerability_simplified.md | 32 ++++-- .../one_vulnerability_standard.md | 26 ++++- .../vulnerabilities_simplified.md | 81 ++++++++++--- .../vulnerabilities_simplified_split1.md | 18 ++- .../vulnerabilities_simplified_split2.md | 79 ++++++++++++- .../vulnerabilities_standard.md | 67 +++++++---- .../vulnerabilities_standard_split1.md | 14 +-- .../vulnerabilities_standard_split2.md | 56 ++++++--- utils/issues/issuescollection.go | 4 +- utils/outputwriter/icons.go | 14 +-- utils/outputwriter/icons_test.go | 28 +++-- utils/outputwriter/markdowntable_test.go | 23 ++-- utils/outputwriter/outputcontent.go | 16 +-- utils/outputwriter/outputcontent_test.go | 66 +++++++++-- utils/outputwriter/simplifiedoutput_test.go | 14 +-- 56 files changed, 894 insertions(+), 643 deletions(-) create mode 100644 testdata/messages/summarycomment/structure/error_simplified.md create mode 100644 testdata/messages/summarycomment/structure/error_standard.md diff --git a/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_simplified.md b/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_simplified.md index d47789c99..3ac239297 100644 --- a/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_simplified.md +++ b/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_simplified.md @@ -4,18 +4,21 @@ ## 📦🔍 Contextual Analysis CVE --- -| Severity | Impacted Dependency | Finding | CVE | +| Severity | ID | Impacted Dependency | Finding | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| Critical | werkzeug:1.0.1 | The vulnerable function flask.Flask.run is called | CVE-2022-29361 | +| Critical | CVE-2022-29361 | werkzeug:1.0.1 | The vulnerable function flask.Flask.run is called | --- ### Description --- + The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`. + --- ### CVE details --- -cveDetails \ No newline at end of file + +cveDetails diff --git a/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_standard.md b/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_standard.md index d8ed4f938..b04e3f0a8 100644 --- a/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_standard.md +++ b/testdata/messages/reviewcomment/applicable/applicable_review_content_no_remediation_standard.md @@ -2,24 +2,15 @@ ## 📦🔍 Contextual Analysis CVE
-| Severity | Impacted Dependency | Finding | CVE | +| Severity | ID | Impacted Dependency | Finding | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
Critical | werkzeug:1.0.1 | The vulnerable function flask.Flask.run is called | CVE-2022-29361 | +| Critical | CVE-2022-29361 | werkzeug:1.0.1 | The vulnerable function flask.Flask.run is called |
-
- Description -
- +
Description The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`. - -
- -
- CVE details -
- +
+
CVE details cveDetails - -
+
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/applicable/applicable_review_content_simplified.md b/testdata/messages/reviewcomment/applicable/applicable_review_content_simplified.md index 60a38a600..4d4700253 100644 --- a/testdata/messages/reviewcomment/applicable/applicable_review_content_simplified.md +++ b/testdata/messages/reviewcomment/applicable/applicable_review_content_simplified.md @@ -4,24 +4,31 @@ ## 📦🔍 Contextual Analysis CVE --- -| Severity | Impacted Dependency | Finding | CVE | +| Severity | ID | Impacted Dependency | Finding | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| Critical | werkzeug:1.0.1 | The vulnerable function flask.Flask.run is called | CVE-2022-29361 | +| Critical | CVE-2022-29361 | werkzeug:1.0.1 | The vulnerable function flask.Flask.run is called | --- ### Description --- + The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`. + --- ### CVE details --- + cveDetails + --- ### Remediation --- -some remediation \ No newline at end of file + + +some remediation + diff --git a/testdata/messages/reviewcomment/applicable/applicable_review_content_standard.md b/testdata/messages/reviewcomment/applicable/applicable_review_content_standard.md index b02af8388..aad78f2ed 100644 --- a/testdata/messages/reviewcomment/applicable/applicable_review_content_standard.md +++ b/testdata/messages/reviewcomment/applicable/applicable_review_content_standard.md @@ -2,32 +2,20 @@ ## 📦🔍 Contextual Analysis CVE
-| Severity | Impacted Dependency | Finding | CVE | +| Severity | ID | Impacted Dependency | Finding | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
Critical | werkzeug:1.0.1 | The vulnerable function flask.Flask.run is called | CVE-2022-29361 | +| ![critical](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
Critical | CVE-2022-29361 | werkzeug:1.0.1 | The vulnerable function flask.Flask.run is called |
-
- Description -
- +
Description The scanner checks whether the vulnerable `Development Server` of the `werkzeug` library is used by looking for calls to `werkzeug.serving.run_simple()`. - -
- -
- CVE details -
- +
+
CVE details cveDetails - -
- -
- Remediation -
+
+
Remediation some remediation -
+
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/iac/iac_review_content_simplified.md b/testdata/messages/reviewcomment/iac/iac_review_content_simplified.md index c526eb6f4..0f02afac6 100644 --- a/testdata/messages/reviewcomment/iac/iac_review_content_simplified.md +++ b/testdata/messages/reviewcomment/iac/iac_review_content_simplified.md @@ -8,17 +8,18 @@ | :---------------------: | :-----------------------------------: | | Medium | Missing auto upgrade was detected | + --- ### Full description --- -Resource `google_container_node_pool` should have `management.auto_upgrade=true` - -Vulnerable example - -``` -resource "google_container_node_pool" "vulnerable_example" { - management { - auto_upgrade = false - } -} -``` + + + +--- +### Vulnerability Details + +--- +Scanner Description.... + + diff --git a/testdata/messages/reviewcomment/iac/iac_review_content_standard.md b/testdata/messages/reviewcomment/iac/iac_review_content_standard.md index 7c96b0682..b12b95894 100644 --- a/testdata/messages/reviewcomment/iac/iac_review_content_standard.md +++ b/testdata/messages/reviewcomment/iac/iac_review_content_standard.md @@ -4,24 +4,14 @@ | Severity | Finding | | :---------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Missing auto upgrade was detected | +| ![medium](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Missing auto upgrade was detected |
-
- Full description -
-Resource `google_container_node_pool` should have `management.auto_upgrade=true` +
Full description -Vulnerable example - -``` -resource "google_container_node_pool" "vulnerable_example" { - management { - auto_upgrade = false - } -} -``` +### Vulnerability Details +Scanner Description.... - -
+
diff --git a/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md b/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md index c526eb6f4..5e4e2b2dc 100644 --- a/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md +++ b/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md @@ -1,24 +1,25 @@ --- -## 🛠️ Infrastructure as Code Vulnerability +## 🛠️ Infrastructure as Code Violation --- -| Severity | Finding | -| :---------------------: | :-----------------------------------: | -| Medium | Missing auto upgrade was detected | +| Severity | ID | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| Medium | iac-violation-id | Missing auto upgrade was detected | jas-watch | policy1, policy2 | + --- ### Full description --- -Resource `google_container_node_pool` should have `management.auto_upgrade=true` - -Vulnerable example - -``` -resource "google_container_node_pool" "vulnerable_example" { - management { - auto_upgrade = false - } -} -``` + + + +--- +### Violation Details + +--- +Scanner Description.... + + diff --git a/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md b/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md index 8abc2bcbe..ee2114944 100644 --- a/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md +++ b/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md @@ -2,33 +2,16 @@ ## 🛠️ Infrastructure as Code Violation
-| Severity | ID | Finding | -| :---------------------: | :-----------------------------------: |:-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | VIOLATION_ID | Missing auto upgrade was detected | +| Severity | ID | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![medium](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | iac-violation-id | Missing auto upgrade was detected | jas-watch | policy1
policy2 |
-
- Full description -#### Violation Details -| | | -| :--- | :--- | -__Policies:__| xsc-policy-1 -__Watch Name:__| xsc-watch -__CWE:__| CWE-89 +
Full description +### Violation Details +Scanner Description.... -Resource `google_container_node_pool` should have `management.auto_upgrade=true` - -Vulnerable example - -``` -resource "google_container_node_pool" "vulnerable_example" { - management { - auto_upgrade = false - } -} -``` - - -
+
diff --git a/testdata/messages/reviewcomment/sast/sast_review_content_standard.md b/testdata/messages/reviewcomment/sast/sast_review_content_standard.md index f3f955090..ef8888f76 100644 --- a/testdata/messages/reviewcomment/sast/sast_review_content_standard.md +++ b/testdata/messages/reviewcomment/sast/sast_review_content_standard.md @@ -4,7 +4,7 @@ | Severity | Finding | | :---------------------: | :-----------------------------------: | -| ![low](https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/v2/applicableLowSeverity.png)
Low | Found a Use of Insecure Random | +| ![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | Found a Use of Insecure Random |
diff --git a/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md b/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md index 38e5c7030..a44ecd7df 100644 --- a/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md +++ b/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md @@ -4,9 +4,9 @@ | Severity | ID | Finding | Watch Name | Policies | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![low](https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/v2/applicableLowSeverity.png)
Low | sast-violation-id | Found a Use of Insecure Random | jas-watch | policy1
policy2 | -| ![high](https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/v2/applicableHighSeverity.png)
High | sast-violation-id-2 | Found a Use of Insecure Random | jas-watch2 | policy3 | -| ![high](https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/v2/applicableHighSeverity.png)
High | sast-violation-id-3 | Found An Express Not Using Helmet | jas-watch2 | policy3 | +| ![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | sast-violation-id | Found a Use of Insecure Random | jas-watch | policy1
policy2 | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | sast-violation-id-2 | Found a Use of Insecure Random | jas-watch2 | policy3 | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | sast-violation-id-3 | Found An Express Not Using Helmet | jas-watch2 | policy3 |
diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md index 26afae746..70bd2a4f0 100644 --- a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md @@ -1,43 +1,30 @@ --- -## 🗝️ Secret Detected +## 🤫 Secret Vulnerability --- | Severity | Finding | | :---------------------: | :-----------------------------------: | -| Medium | Secret keys were found | +| High | Secret keys were found | + --- ### Full description --- -Storing hardcoded secrets in your source code or binary artifact could lead to several risks. - -If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. - -## Best practices - -Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - -* ### Environment Variables -Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - -`SECRET_VAR=MySecret ./my_application` -This way, `MySecret` does not have to be hardcoded into `my_application`. -Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. - -* ### Secret management services +--- +### Vulnerability Details -External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - +--- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798, CWE-799 | +| **Abbreviation:** | rule-id | -* [Hashicorp Vault](https://www.vaultproject.io) -* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) -* [Google Cloud KMS](https://cloud.google.com/security-key-management) +Scanner Description.... -## Least-privilege principle -Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. -For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. -That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. \ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md index 5cd7222ef..70498c887 100644 --- a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md @@ -1,46 +1,22 @@ -## 🗝️ Secret Detected +## 🤫 Secret Vulnerability
| Severity | Finding | | :---------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Secret keys were found | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | Secret keys were found |
-
- Full description -
-Storing hardcoded secrets in your source code or binary artifact could lead to several risks. +
Full description -If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798
CWE-799 | +| **Abbreviation:** | rule-id | -## Best practices +Scanner Description.... -Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - - -* ### Environment Variables - -Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - -`SECRET_VAR=MySecret ./my_application` -This way, `MySecret` does not have to be hardcoded into `my_application`. - -Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. - -* ### Secret management services - -External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - - -* [Hashicorp Vault](https://www.vaultproject.io) -* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) -* [Google Cloud KMS](https://cloud.google.com/security-key-management) - -## Least-privilege principle - -Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. -For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. -That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. - - -
\ No newline at end of file +
diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md b/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md index 82a12945f..4accdb693 100644 --- a/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md @@ -1,43 +1,30 @@ --- -## 🗝️ Secret Detected +## 🤫 Secret Vulnerability --- -| Severity | Finding | Status | +| Severity | Status | Finding | | :---------------------: | :-----------------------------------: | :-----------------------------------: | -| Medium | Secret keys were found | Active | +| High | Active | Secret keys were found | + --- ### Full description --- -Storing hardcoded secrets in your source code or binary artifact could lead to several risks. - -If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. - -## Best practices - -Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - -* ### Environment Variables -Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - -`SECRET_VAR=MySecret ./my_application` -This way, `MySecret` does not have to be hardcoded into `my_application`. -Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. - -* ### Secret management services +--- +### Vulnerability Details -External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - +--- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798, CWE-799 | +| **Abbreviation:** | rule-id | -* [Hashicorp Vault](https://www.vaultproject.io) -* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) -* [Google Cloud KMS](https://cloud.google.com/security-key-management) +Scanner Description.... -## Least-privilege principle -Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. -For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. -That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. \ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md b/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md index ff4376e74..127b655c1 100644 --- a/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md @@ -1,46 +1,22 @@ -## 🗝️ Secret Detected +## 🤫 Secret Vulnerability
-| Severity | Finding | Status | +| Severity | Status | Finding | | :---------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Secret keys were found | Active | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | Active | Secret keys were found |
-
- Full description -
-Storing hardcoded secrets in your source code or binary artifact could lead to several risks. +
Full description -If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798
CWE-799 | +| **Abbreviation:** | rule-id | -## Best practices +Scanner Description.... -Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - - -* ### Environment Variables - -Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - -`SECRET_VAR=MySecret ./my_application` -This way, `MySecret` does not have to be hardcoded into `my_application`. - -Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. - -* ### Secret management services - -External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - - -* [Hashicorp Vault](https://www.vaultproject.io) -* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) -* [Google Cloud KMS](https://cloud.google.com/security-key-management) - -## Least-privilege principle - -Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. -For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. -That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. - - -
\ No newline at end of file +
diff --git a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md index 82a12945f..a9c50edc5 100644 --- a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md +++ b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md @@ -1,43 +1,31 @@ --- -## 🗝️ Secret Detected +## 🤫 Secret Violation --- -| Severity | Finding | Status | -| :---------------------: | :-----------------------------------: | :-----------------------------------: | -| Medium | Secret keys were found | Active | +| Severity | ID | Status | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| High | secret-violation-id | Active | Secret keys were found | jas-watch | policy1 | +| Critical | secret-violation-id-2 | Inactive | Secret keys were found | jas-watch2 | policy1, policy2 | + --- ### Full description --- -Storing hardcoded secrets in your source code or binary artifact could lead to several risks. - -If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. - -## Best practices - -Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - -* ### Environment Variables -Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - -`SECRET_VAR=MySecret ./my_application` -This way, `MySecret` does not have to be hardcoded into `my_application`. -Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. - -* ### Secret management services +--- +### Violation Details -External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - +--- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798, CWE-799 | +| **Abbreviation:** | rule-id | -* [Hashicorp Vault](https://www.vaultproject.io) -* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) -* [Google Cloud KMS](https://cloud.google.com/security-key-management) +Scanner Description.... -## Least-privilege principle -Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. -For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. -That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. \ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md index 1046afafb..7e4709dec 100644 --- a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md +++ b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md @@ -1,54 +1,23 @@ -## 🗝️ Secret Violation +## 🤫 Secret Violation
-| Severity |ID | Finding | Status | -| :---------------------: | :-----------------------------------: |:-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | VIOLATION_ID | Secret keys were found | Active | +| Severity | ID | Status | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | secret-violation-id | Active | Secret keys were found | jas-watch | policy1 | +| ![critical (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | secret-violation-id-2 | Inactive | Secret keys were found | jas-watch2 | policy1
policy2 |
-
- Full description -### Violation Details -| | | -| :--- | :--- | -__Policies:__| xsc-policy-1 -__Watch Name:__| xsc-watch -__CWE:__| CWE-89 -__Abbreviation:__| java-sql-injection - - -Storing hardcoded secrets in your source code or binary artifact could lead to several risks. - -If the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication. - -## Best practices - -Use safe storage when storing high-privilege secrets such as passwords and tokens, for example - - -* ### Environment Variables +
Full description -Environment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example - -`SECRET_VAR=MySecret ./my_application` -This way, `MySecret` does not have to be hardcoded into `my_application`. - -Note that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument. - -* ### Secret management services - -External vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include - - -* [Hashicorp Vault](https://www.vaultproject.io) -* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service) -* [Google Cloud KMS](https://cloud.google.com/security-key-management) - -## Least-privilege principle - -Storing a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application. -For example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated. -That being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret. +### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798
CWE-799 | +| **Abbreviation:** | rule-id | +Scanner Description.... -
\ No newline at end of file +
diff --git a/testdata/messages/summarycomment/structure/error_simplified.md b/testdata/messages/summarycomment/structure/error_simplified.md new file mode 100644 index 000000000..00fc7499d --- /dev/null +++ b/testdata/messages/summarycomment/structure/error_simplified.md @@ -0,0 +1,39 @@ + + +[comment]: <> (FrogbotReviewComment) + + + + +--- +## 📗 Scan Summary + +--- +- Frogbot attempted to scan for but encountered an error. + +--- +### ⚠️ Error Details + +--- + + + +--- +#### Error: + +--- +Some error that occurred in scan + + +--- +#### Next Steps: + +--- +1. Please try to rerun the scan. +2. If the issue persists, consider checking the [Frogbot documentation](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) for troubleshooting tips. +3. If you still need assistance, feel free to reach out to [JFrog Support](https://jfrog.com/support/). + +Thank you for your understanding! + +--- +[🐸 JFrog Frogbot](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) \ No newline at end of file diff --git a/testdata/messages/summarycomment/structure/error_standard.md b/testdata/messages/summarycomment/structure/error_standard.md new file mode 100644 index 000000000..67fff3b91 --- /dev/null +++ b/testdata/messages/summarycomment/structure/error_standard.md @@ -0,0 +1,26 @@ + + +[comment]: <> (FrogbotReviewComment) + + + +## 📗 Scan Summary +- Frogbot attempted to scan for but encountered an error. +
⚠️ Error Details + +#### Error: +Some error that occurred in scan + +#### Next Steps: +1. Please try to rerun the scan. +2. If the issue persists, consider checking the [Frogbot documentation](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) for troubleshooting tips. +3. If you still need assistance, feel free to reach out to [JFrog Support](https://jfrog.com/support/). + +Thank you for your understanding!
+ +--- +
+ +[🐸 JFrog Frogbot](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) + +
diff --git a/testdata/messages/summarycomment/structure/fix_mr_not_entitled.md b/testdata/messages/summarycomment/structure/fix_mr_not_entitled.md index 2d5fafec7..a9209d088 100644 --- a/testdata/messages/summarycomment/structure/fix_mr_not_entitled.md +++ b/testdata/messages/summarycomment/structure/fix_mr_not_entitled.md @@ -12,9 +12,7 @@ ``` some content ``` -
- Note: - +
Note ---
@@ -22,10 +20,7 @@ some content **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system.
- - -
- +
---
diff --git a/testdata/messages/summarycomment/structure/fix_pr_not_entitled.md b/testdata/messages/summarycomment/structure/fix_pr_not_entitled.md index 8e10ad30f..5c733d825 100644 --- a/testdata/messages/summarycomment/structure/fix_pr_not_entitled.md +++ b/testdata/messages/summarycomment/structure/fix_pr_not_entitled.md @@ -12,9 +12,7 @@ ``` some content ``` -
- Note: - +
Note ---
@@ -22,10 +20,7 @@ some content **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system.
- - -
- +
---
diff --git a/testdata/messages/summarycomment/structure/fix_simplified_not_entitled.md b/testdata/messages/summarycomment/structure/fix_simplified_not_entitled.md index 6b9aeb3fa..e646ce1a1 100644 --- a/testdata/messages/summarycomment/structure/fix_simplified_not_entitled.md +++ b/testdata/messages/summarycomment/structure/fix_simplified_not_entitled.md @@ -7,11 +7,7 @@ ``` some content ``` - ---- -Note: - ---- +Note: --- **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system. diff --git a/testdata/messages/summarycomment/structure/summary_comment_issues_mr_not_entitled.md b/testdata/messages/summarycomment/structure/summary_comment_issues_mr_not_entitled.md index c0d9be271..1854bf32b 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_issues_mr_not_entitled.md +++ b/testdata/messages/summarycomment/structure/summary_comment_issues_mr_not_entitled.md @@ -12,9 +12,7 @@ ``` some content ``` -
- Note: - +
Note ---
@@ -22,10 +20,7 @@ some content **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system.
- - -
- +
---
diff --git a/testdata/messages/summarycomment/structure/summary_comment_issues_pr_not_entitled.md b/testdata/messages/summarycomment/structure/summary_comment_issues_pr_not_entitled.md index 8011e0be6..8de14eb5b 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_issues_pr_not_entitled.md +++ b/testdata/messages/summarycomment/structure/summary_comment_issues_pr_not_entitled.md @@ -12,9 +12,7 @@ ``` some content ``` -
- Note: - +
Note ---
@@ -22,10 +20,7 @@ some content **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system.
- - -
- +
---
diff --git a/testdata/messages/summarycomment/structure/summary_comment_issues_pr_not_entitled_with_title.md b/testdata/messages/summarycomment/structure/summary_comment_issues_pr_not_entitled_with_title.md index 96c89e5c7..428ee98d1 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_issues_pr_not_entitled_with_title.md +++ b/testdata/messages/summarycomment/structure/summary_comment_issues_pr_not_entitled_with_title.md @@ -13,9 +13,7 @@ ``` some content ``` -
- Note: - +
Note ---
@@ -23,10 +21,7 @@ some content **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system.
- - -
- +
---
diff --git a/testdata/messages/summarycomment/structure/summary_comment_issues_simplified_not_entitled.md b/testdata/messages/summarycomment/structure/summary_comment_issues_simplified_not_entitled.md index ed09e547e..94fad5000 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_issues_simplified_not_entitled.md +++ b/testdata/messages/summarycomment/structure/summary_comment_issues_simplified_not_entitled.md @@ -7,11 +7,7 @@ ``` some content ``` - ---- -Note: - ---- +Note: --- **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system. diff --git a/testdata/messages/summarycomment/structure/summary_comment_no_issues_mr_not_entitled.md b/testdata/messages/summarycomment/structure/summary_comment_no_issues_mr_not_entitled.md index a58ad8a9d..269ca7943 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_no_issues_mr_not_entitled.md +++ b/testdata/messages/summarycomment/structure/summary_comment_no_issues_mr_not_entitled.md @@ -8,9 +8,7 @@
-
- Note: - +
Note ---
@@ -18,10 +16,7 @@ **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system.
- - -
- +
---
diff --git a/testdata/messages/summarycomment/structure/summary_comment_no_issues_pr_not_entitled.md b/testdata/messages/summarycomment/structure/summary_comment_no_issues_pr_not_entitled.md index 3c88b0a97..225fea261 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_no_issues_pr_not_entitled.md +++ b/testdata/messages/summarycomment/structure/summary_comment_no_issues_pr_not_entitled.md @@ -8,9 +8,7 @@
-
- Note: - +
Note ---
@@ -18,10 +16,7 @@ **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system.
- - -
- +
---
diff --git a/testdata/messages/summarycomment/structure/summary_comment_no_issues_pr_not_entitled_with_title.md b/testdata/messages/summarycomment/structure/summary_comment_no_issues_pr_not_entitled_with_title.md index 3382eeb5f..62a6afc04 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_no_issues_pr_not_entitled_with_title.md +++ b/testdata/messages/summarycomment/structure/summary_comment_no_issues_pr_not_entitled_with_title.md @@ -9,9 +9,7 @@
## **Custom title** -
- Note: - +
Note ---
@@ -19,10 +17,7 @@ **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system.
- - -
- +
---
diff --git a/testdata/messages/summarycomment/structure/summary_comment_no_issues_simplified_not_entitled.md b/testdata/messages/summarycomment/structure/summary_comment_no_issues_simplified_not_entitled.md index f55ec3edb..6340da3d1 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_no_issues_simplified_not_entitled.md +++ b/testdata/messages/summarycomment/structure/summary_comment_no_issues_simplified_not_entitled.md @@ -3,11 +3,7 @@ [comment]: <> (FrogbotReviewComment) **👍 Frogbot scanned this pull request and did not find any new security issues.** - ---- -Note: - ---- +Note: --- **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system. diff --git a/testdata/messages/summarycomment/structure/summary_comment_no_issues_simplified_not_entitled_with_title.md b/testdata/messages/summarycomment/structure/summary_comment_no_issues_simplified_not_entitled_with_title.md index a6830f144..6039ac225 100644 --- a/testdata/messages/summarycomment/structure/summary_comment_no_issues_simplified_not_entitled_with_title.md +++ b/testdata/messages/summarycomment/structure/summary_comment_no_issues_simplified_not_entitled_with_title.md @@ -8,11 +8,7 @@ ## **Custom title** --- - ---- -Note: - ---- +Note: --- **Frogbot** also supports **Contextual Analysis, Secret Detection, IaC and SAST Vulnerabilities Scanning**. This features are included as part of the [JFrog Advanced Security](https://jfrog.com/advanced-security) package, which isn't enabled on your system. diff --git a/testdata/messages/summarycomment/summary/summary_error_simplified.md b/testdata/messages/summarycomment/summary/summary_error_simplified.md index 8b1378917..a090e66b8 100644 --- a/testdata/messages/summarycomment/summary/summary_error_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_error_simplified.md @@ -1 +1,7 @@ + +--- +## 📗 Scan Summary + +--- +- Frogbot attempted to scan for but encountered an error. \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_error_standard.md b/testdata/messages/summarycomment/summary/summary_error_standard.md index 8b1378917..9544038d0 100644 --- a/testdata/messages/summarycomment/summary/summary_error_standard.md +++ b/testdata/messages/summarycomment/summary/summary_error_standard.md @@ -1 +1,3 @@ +## 📗 Scan Summary +- Frogbot attempted to scan for but encountered an error. \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_simplified.md b/testdata/messages/summarycomment/summary/summary_simplified.md index e6b858601..e989c77da 100644 --- a/testdata/messages/summarycomment/summary/summary_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_simplified.md @@ -1,12 +1,15 @@ + + +--- ## 📗 Scan Summary -- Frogbot scanned for violations and found 17 violations. - -| Scan Category | Status | Security Issues -| :--- | :--- | :--- | -__Software Composition Analysis__ | ✅ Done |
__17 Issues Found__❗️ 5 Critical
🔴 9 High
🟠 3 Medium -__Contextual Analysis__ | ✅ Done -__Static Application Security Testing (SAST)__ | ✅ Done | __Not Found__ -__Secrets__ | ❌ Failed -__Infrastucture as Code (IaC)__ | ℹ️ Not Scanned -
-
+ +--- +- Frogbot scanned for and found 9 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done | 6 Issues Found: ❗️ 1 Critical, 🔴 2 High, 🟠 1 Medium, 🟡 1 Low, ⚪️ 1 Unknown | +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done | 3 Issues Found: 🔴 2 High, 🟡 1 Low | +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_standard.md b/testdata/messages/summarycomment/summary/summary_standard.md index e6b858601..e0d63cbcf 100644 --- a/testdata/messages/summarycomment/summary/summary_standard.md +++ b/testdata/messages/summarycomment/summary/summary_standard.md @@ -1,12 +1,11 @@ + ## 📗 Scan Summary -- Frogbot scanned for violations and found 17 violations. +- Frogbot scanned for and found 9 issues -| Scan Category | Status | Security Issues -| :--- | :--- | :--- | -__Software Composition Analysis__ | ✅ Done |
__17 Issues Found__❗️ 5 Critical
🔴 9 High
🟠 3 Medium -__Contextual Analysis__ | ✅ Done -__Static Application Security Testing (SAST)__ | ✅ Done | __Not Found__ -__Secrets__ | ❌ Failed -__Infrastucture as Code (IaC)__ | ℹ️ Not Scanned -
-
+| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done |
6 Issues Found 1 Critical
2 High
1 Medium
1 Low
1 Unknown
| +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done |
3 Issues Found 2 High
1 Low
| +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_violation_standard.md b/testdata/messages/summarycomment/summary/summary_violation_standard.md index e6b858601..e0d63cbcf 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_standard.md +++ b/testdata/messages/summarycomment/summary/summary_violation_standard.md @@ -1,12 +1,11 @@ + ## 📗 Scan Summary -- Frogbot scanned for violations and found 17 violations. +- Frogbot scanned for and found 9 issues -| Scan Category | Status | Security Issues -| :--- | :--- | :--- | -__Software Composition Analysis__ | ✅ Done |
__17 Issues Found__❗️ 5 Critical
🔴 9 High
🟠 3 Medium -__Contextual Analysis__ | ✅ Done -__Static Application Security Testing (SAST)__ | ✅ Done | __Not Found__ -__Secrets__ | ❌ Failed -__Infrastucture as Code (IaC)__ | ℹ️ Not Scanned -
-
+| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done |
6 Issues Found 1 Critical
2 High
1 Medium
1 Low
1 Unknown
| +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done |
3 Issues Found 2 High
1 Low
| +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/violations/license/license_violation_simplified.md b/testdata/messages/summarycomment/violations/license/license_violation_simplified.md index 9ab5310f6..a1297e325 100644 --- a/testdata/messages/summarycomment/violations/license/license_violation_simplified.md +++ b/testdata/messages/summarycomment/violations/license/license_violation_simplified.md @@ -1,10 +1,69 @@ --- -## ⚖️ Violated Licenses +## 🚥 Policy Violations --- -| SEVERITY | LICENSE | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | -| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| High | License1 | Comp1 1.0 | Dep1 2.0 | -| High | License2 | root 1.0.0, minimatch 1.2.3 | Dep2 3.0 | \ No newline at end of file + + + +--- +### ⚖️ License Violations + +--- + +| Severity | License | Direct Dependencies | Impacted Dependency | Watch Name | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| High | License1 | Comp1:1.0 | Dep1:2.0 | watch | +| High | License2 | root:1.0.0 | Dep2:3.0 | watch2 | +| | | minimatch:1.2.3 | | | + + +--- +### 🔖 Details + +--- + + + +--- +#### [ License1 ] Dep1 2.0 (watch) + +--- + + + +--- +### Violation Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Policies:** | policy1, policy2 | +| **Watch Name:** | watch | +| **Direct Dependencies:** | Comp1:1.0 | +| **Impacted Dependency:** | Dep1:2.0 | +| **Full Name:** | License1 full name | + + + + +--- +#### [ License2 ] Dep2 3.0 (watch2) + +--- + + + +--- +### Violation Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Policies:** | policy3 | +| **Watch Name:** | watch2 | +| **Direct Dependencies:** | root:1.0.0, minimatch:1.2.3 | +| **Impacted Dependency:** | Dep2:3.0 | +| **Full Name:** | - | + diff --git a/testdata/messages/summarycomment/violations/license/license_violation_standard.md b/testdata/messages/summarycomment/violations/license/license_violation_standard.md index 5eef28a61..1aecc3806 100644 --- a/testdata/messages/summarycomment/violations/license/license_violation_standard.md +++ b/testdata/messages/summarycomment/violations/license/license_violation_standard.md @@ -1,24 +1,44 @@ + ## 🚥 Policy Violations + ### ⚖️ License Violations -
+
-| Severity | License | Direct Dependency | Impacted Dependency | Watch Name | -| :---: | :---: | :---: | :---: | :---: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | BSD-3-Clause | werkzeug:1.0.1 | pyyaml:1.1.1 | xsc-watch | +| Severity | License | Direct Dependencies | Impacted Dependency | Watch Name | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | License1 | Comp1:1.0 | Dep1:2.0 | watch | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | License2 | root:1.0.0
minimatch:1.2.3 | Dep2:3.0 | watch2 |
-#### 🔖 Details -
-[ BSD-3-Clause ] pyyaml 1.1.1 (xsc-watch) +### 🔖 Details + + +
[ License1 ] Dep1 2.0 (watch) + +### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **Policies:** | policy1, policy2 | +| **Watch Name:** | watch | +| **Direct Dependencies:** | Comp1:1.0 | +| **Impacted Dependency:** | Dep1:2.0 | +| **Full Name:** | License1 full name | + +
+ +
[ License2 ] Dep2 3.0 (watch2) -| | | -| :--- | :--- | -**Policies:** | xsc-policy-1 -**Full Name:** | BSD 3-Clause "New" or "Revised" License +### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **Policies:** | policy3 | +| **Watch Name:** | watch2 | +| **Direct Dependencies:** | root:1.0.0, minimatch:1.2.3 | +| **Impacted Dependency:** | Dep2:3.0 | +| **Full Name:** | - | -
-
\ No newline at end of file +
\ No newline at end of file diff --git a/testdata/messages/summarycomment/violations/security/security_violation_simplified.md b/testdata/messages/summarycomment/violations/security/security_violation_simplified.md index b9e45ec1b..159980804 100644 --- a/testdata/messages/summarycomment/violations/security/security_violation_simplified.md +++ b/testdata/messages/summarycomment/violations/security/security_violation_simplified.md @@ -1,25 +1,106 @@ -## 🚥 Policy Violations + +--- ### 🚨 Security Violations -
+--- -| Severity/Risk | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Watch Name | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Watch Name | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | sca-watch | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | +| Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0 | impacted:3.0.0 | - | +| | | | dep2:2.0.0 | | | +| High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | - | +| Medium | CVE-2022-26652, CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D:v0.21.0 | - | +| Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3:v3.5.1 | - | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | CVE-2022-26652
CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | -
+--- +### 🔖 Details +--- -### 🔖 Details -
- [ CVE-1111-11111 ] impacted 3.0.0 (sca-watch) -### Violation Details\n\n| | |\n| :--- | :--- |\n__Policies:__| xsc-policy-1\n__Watch Name:__| xsc-watch\n__JFrog Research Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/critical.svg) Critical\n__Contextual Analysis:__| Applicable\n__Direct Dependencies:__| flask:1.1.2\n__Impacted Dependency:__| werkzeug:1.0.1\n__Fix Versions:__| 4.0.0, 5.0.0\n__CVSS V3:__| 9.8\n\nsome-summary\n\n### 🔬 JFrog Rese -arch Details\n\n**Description:**\nSummary XRAY-122345\n\n**Remediation:**\nsome remediation\n\n
\n\n
\n [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 \n
\n\n\n**Description:**\nSummary XRAY-122345\n\n**Remediation:**\nsome remediation\n\n
\n\n
\n [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 \n
\n\n\n**Remediation:**\nsome remediation\n\n
\n\n
\n github.com/mholt/archiver/v3 v3.5.1 \n
\n\n\n**Description:**\nSummary\n\n
\n \ No newline at end of file +--- +#### [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 + +--- + + + +--- +### Violation Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Impacted Dependency:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Fixed Versions:** | [0.24.1] | +| **CVSS V3:** | - | + +Summary XRAY-122345 + + +--- +### 🔬 JFrog Research Details + +--- + +**Remediation:** +some remediation + + + +--- +#### [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 + +--- + + + +--- +### Violation Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Applicable | +| **Direct Dependencies:** | component-D:v0.21.0 | +| **Impacted Dependency:** | component-D:v0.21.0 | +| **Fixed Versions:** | [0.24.3] | +| **CVSS V3:** | - | + + +--- +### 🔬 JFrog Research Details + +--- + +**Remediation:** +some remediation + + + +--- +#### github.com/mholt/archiver/v3 v3.5.1 + +--- + + + +--- +### Violation Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Impacted Dependency:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Fixed Versions:** | - | +| **CVSS V3:** | - | + +Summary \ No newline at end of file diff --git a/testdata/messages/summarycomment/violations/security/security_violation_standard.md b/testdata/messages/summarycomment/violations/security/security_violation_standard.md index d12fe4839..55b1d196b 100644 --- a/testdata/messages/summarycomment/violations/security/security_violation_standard.md +++ b/testdata/messages/summarycomment/violations/security/security_violation_standard.md @@ -1,79 +1,67 @@ -## 🚥 Policy Violations ### 🚨 Security Violations
-| Severity/Risk | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Watch Name | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Watch Name | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | sca-watch | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | CVE-2022-26652
CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | +| ![critical (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted:3.0.0 | - | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server:v0.21.0 | - | +| ![medium](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | CVE-2022-26652
CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D:v0.21.0 | - | +| ![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3:v3.5.1 | - |
### 🔖 Details -
- [ CVE-1111-11111 ] impacted 3.0.0 (sca-watch) -### Violation Details - -| | | -| :--- | :--- | -__Policies:__| xsc-policy-1 -__Watch Name:__| xsc-watch -__JFrog Research Severity:__| ![](https://raw.githubusercontent.com/jfrog/jfrog-ide-webview/main/src/assets/icons/severity/critical.svg) Critical -__Contextual Analysis:__| Applicable -__Direct Dependencies:__| flask:1.1.2 -__Impacted Dependency:__| werkzeug:1.0.1 -__Fix Versions:__| 4.0.0, 5.0.0 -__CVSS V3:__| 9.8 - -some-summary +
[ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 -### 🔬 JFrog Research Details +### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Impacted Dependency:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Fixed Versions:** | [0.24.1] | +| **CVSS V3:** | - | -**Description:** Summary XRAY-122345 -**Remediation:** -some remediation - -
- -
- [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 -
- - -**Description:** -Summary XRAY-122345 +### 🔬 JFrog Research Details **Remediation:** some remediation +
-
+
[ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 -
- [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 -
+### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Applicable | +| **Direct Dependencies:** | component-D:v0.21.0 | +| **Impacted Dependency:** | component-D:v0.21.0 | +| **Fixed Versions:** | [0.24.3] | +| **CVSS V3:** | - | +### 🔬 JFrog Research Details **Remediation:** some remediation +
-
+
github.com/mholt/archiver/v3 v3.5.1 -
- github.com/mholt/archiver/v3 v3.5.1 -
- - -**Description:** -Summary - -
+### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Impacted Dependency:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Fixed Versions:** | - | +| **CVSS V3:** | - | + +Summary
\ No newline at end of file diff --git a/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_no_details_simplified.md b/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_no_details_simplified.md index f032744fd..899a11a99 100644 --- a/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_no_details_simplified.md +++ b/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_no_details_simplified.md @@ -1,15 +1,11 @@ ---- -## 📦 Vulnerable Dependencies --- - +### 📦 Vulnerable Dependencies --- -### ✍️ Summary ---- -| SEVERITY | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| Medium | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.3] | CVE-2022-26652 | \ No newline at end of file +| Medium | CVE-2022-26652 | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.3] | \ No newline at end of file diff --git a/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_no_details_standard.md b/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_no_details_standard.md index b194f1f7c..09cfb721e 100644 --- a/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_no_details_standard.md +++ b/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_no_details_standard.md @@ -1,11 +1,11 @@ -## 📦 Vulnerable Dependencies -### ✍️ Summary +### 📦 Vulnerable Dependencies +
-| SEVERITY | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.3] | CVE-2022-26652 | +| Medium | CVE-2022-26652 | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.3] |
diff --git a/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_simplified.md b/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_simplified.md index e2ec84fb2..41609c4b6 100644 --- a/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_simplified.md +++ b/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_simplified.md @@ -1,28 +1,46 @@ + --- -## 📦 Vulnerable Dependencies +### 📦 Vulnerable Dependencies --- +| Severity | ID | Direct Dependencies | Impacted Dependency | Fixed Versions | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| Medium | CVE-2022-26652 | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.3] | + --- -### ✍️ Summary +### 🔖 Details --- -| SEVERITY | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | -| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| Medium | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.3] | CVE-2022-26652 | + + --- -### 🔬 Research Details +### Vulnerability Details --- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Impacted Dependency:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Fixed Versions:** | [0.24.3] | +| **CVSS V3:** | - | +Summary CVE-2022-26652 + + +--- +### 🔬 JFrog Research Details + +--- **Description:** Research CVE-2022-26652 details **Remediation:** -some remediation \ No newline at end of file +some remediation diff --git a/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_standard.md b/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_standard.md index 7a9f1c5fa..535a6abb1 100644 --- a/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_standard.md +++ b/testdata/messages/summarycomment/vulnerabilities/one_vulnerability_standard.md @@ -1,21 +1,35 @@ -## 📦 Vulnerable Dependencies -### ✍️ Summary +### 📦 Vulnerable Dependencies +
-| SEVERITY | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.3] | CVE-2022-26652 | +| Medium | CVE-2022-26652 | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.3] |
-### 🔬 Research Details +### 🔖 Details + + + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Impacted Dependency:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Fixed Versions:** | [0.24.3] | +| **CVSS V3:** | - | + +Summary CVE-2022-26652 +### 🔬 JFrog Research Details **Description:** Research CVE-2022-26652 details **Remediation:** -some remediation \ No newline at end of file +some remediation diff --git a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified.md b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified.md index 31866fcc6..cc77fc444 100644 --- a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified.md +++ b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified.md @@ -1,53 +1,106 @@ + --- -## 📦 Vulnerable Dependencies +### 📦 Vulnerable Dependencies --- +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0 | impacted 3.0.0 | 4.0.0, 5.0.0 | +| | | | dep2:2.0.0 | | | +| High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | +| Medium | CVE-2022-26652, CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | +| Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | + --- -### ✍️ Summary +### 🔖 Details --- -| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | -| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| Critical | Not Applicable | dep1:1.0.0 | impacted 3.0.0 | 4.0.0, 5.0.0 | CVE-1111-11111 | -| | | dep2:2.0.0 | | | | -| High | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | - | -| Medium | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | CVE-2022-26652, CVE-2023-4321 | -| Low | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | - | + --- -### 🔬 Research Details +#### [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 --- + --- -#### [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 +### Vulnerability Details --- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Impacted Dependency:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Fixed Versions:** | [0.24.1] | +| **CVSS V3:** | - | -**Description:** Summary XRAY-122345 + +--- +### 🔬 JFrog Research Details + +--- + **Remediation:** some remediation + + --- #### [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 --- + + +--- +### Vulnerability Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Applicable | +| **Direct Dependencies:** | component-D:v0.21.0 | +| **Impacted Dependency:** | component-D:v0.21.0 | +| **Fixed Versions:** | [0.24.3] | +| **CVSS V3:** | - | + + +--- +### 🔬 JFrog Research Details + +--- + **Remediation:** some remediation + + +--- +#### github.com/mholt/archiver/v3 v3.5.1 + +--- + + + --- -#### github.com/mholt/archiver/v3 v3.5.1 +### Vulnerability Details --- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Impacted Dependency:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Fixed Versions:** | - | +| **CVSS V3:** | - | -**Description:** Summary \ No newline at end of file diff --git a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified_split1.md b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified_split1.md index e3e2343ed..237e2b70a 100644 --- a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified_split1.md +++ b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified_split1.md @@ -1,19 +1,15 @@ ---- -## 📦 Vulnerable Dependencies --- - +### 📦 Vulnerable Dependencies --- -### ✍️ Summary ---- -| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| Critical | Not Applicable | dep1:1.0.0 | impacted 3.0.0 | 4.0.0, 5.0.0 | CVE-1111-11111 | -| | | dep2:2.0.0 | | | | -| High | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | - | -| Medium | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | CVE-2022-26652, CVE-2023-4321 | -| Low | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | - | \ No newline at end of file +| Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0 | impacted 3.0.0 | 4.0.0, 5.0.0 | +| | | | dep2:2.0.0 | | | +| High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | +| Medium | CVE-2022-26652, CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | +| Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified_split2.md b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified_split2.md index 01b482e36..57e2fae2e 100644 --- a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified_split2.md +++ b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_simplified_split2.md @@ -2,34 +2,105 @@ --- -### 🔬 Research Details +### 📦 Vulnerable Dependencies --- + +--- +### 🔖 Details + +--- + + + --- #### [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 --- -**Description:** + + +--- +### Vulnerability Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Impacted Dependency:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Fixed Versions:** | [0.24.1] | +| **CVSS V3:** | - | + Summary XRAY-122345 + +--- +### 🔬 JFrog Research Details + +--- + **Remediation:** some remediation + + +--- +### 🔖 Details + +--- + + + --- #### [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 --- + + +--- +### Vulnerability Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Applicable | +| **Direct Dependencies:** | component-D:v0.21.0 | +| **Impacted Dependency:** | component-D:v0.21.0 | +| **Fixed Versions:** | [0.24.3] | +| **CVSS V3:** | - | + + +--- +### 🔬 JFrog Research Details + +--- + **Remediation:** some remediation + + +--- +#### github.com/mholt/archiver/v3 v3.5.1 + +--- + + + --- -#### github.com/mholt/archiver/v3 v3.5.1 +### Vulnerability Details --- +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Impacted Dependency:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Fixed Versions:** | - | +| **CVSS V3:** | - | -**Description:** Summary \ No newline at end of file diff --git a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard.md b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard.md index a9f78101c..1280d16b8 100644 --- a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard.md +++ b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard.md @@ -1,50 +1,67 @@ -## 📦 Vulnerable Dependencies -### ✍️ Summary +### 📦 Vulnerable Dependencies +
-| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | 4.0.0
5.0.0 | CVE-1111-11111 | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | - | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | CVE-2022-26652
CVE-2023-4321 | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | - | +| ![critical (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | 4.0.0
5.0.0 | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | +| ![medium](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | CVE-2022-26652
CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | +| ![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - |
-### 🔬 Research Details +### 🔖 Details + -
- [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 -
+
[ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Impacted Dependency:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Fixed Versions:** | [0.24.1] | +| **CVSS V3:** | - | -**Description:** Summary XRAY-122345 +### 🔬 JFrog Research Details + **Remediation:** some remediation +
-
+
[ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 -
- [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 -
+### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Applicable | +| **Direct Dependencies:** | component-D:v0.21.0 | +| **Impacted Dependency:** | component-D:v0.21.0 | +| **Fixed Versions:** | [0.24.3] | +| **CVSS V3:** | - | +### 🔬 JFrog Research Details **Remediation:** some remediation +
-
- -
- github.com/mholt/archiver/v3 v3.5.1 -
- +
github.com/mholt/archiver/v3 v3.5.1 -**Description:** -Summary +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Impacted Dependency:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Fixed Versions:** | - | +| **CVSS V3:** | - | -
+Summary
\ No newline at end of file diff --git a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard_split1.md b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard_split1.md index bbe11a316..11e307420 100644 --- a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard_split1.md +++ b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard_split1.md @@ -1,14 +1,14 @@ -## 📦 Vulnerable Dependencies -### ✍️ Summary +### 📦 Vulnerable Dependencies +
-| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | 4.0.0
5.0.0 | CVE-1111-11111 | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | - | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | CVE-2022-26652
CVE-2023-4321 | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - | - | +| ![critical (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-1111-11111 | Not Applicable | dep1:1.0.0
dep2:2.0.0 | impacted 3.0.0 | 4.0.0
5.0.0 | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | XRAY-122345 | Undetermined | github.com/nats-io/nats-streaming-server:v0.21.0 | github.com/nats-io/nats-streaming-server v0.21.0 | [0.24.1] | +| ![medium](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | CVE-2022-26652
CVE-2023-4321 | Applicable | component-D:v0.21.0 | component-D v0.21.0 | [0.24.3] | +| ![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low | - | Undetermined | github.com/mholt/archiver/v3:v3.5.1 | github.com/mholt/archiver/v3 v3.5.1 | - |
diff --git a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard_split2.md b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard_split2.md index c9114db28..40be1e952 100644 --- a/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard_split2.md +++ b/testdata/messages/summarycomment/vulnerabilities/vulnerabilities_standard_split2.md @@ -1,36 +1,56 @@ -### 🔬 Research Details +### 📦 Vulnerable Dependencies -
- [ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 -
+### 🔖 Details + + +
[ XRAY-122345 ] github.com/nats-io/nats-streaming-server v0.21.0 + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Impacted Dependency:** | github.com/nats-io/nats-streaming-server:v0.21.0 | +| **Fixed Versions:** | [0.24.1] | +| **CVSS V3:** | - | -**Description:** Summary XRAY-122345 +### 🔬 JFrog Research Details + **Remediation:** some remediation +
-
+
[ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 -
- [ CVE-2022-26652, CVE-2023-4321 ] component-D v0.21.0 -
+### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Applicable | +| **Direct Dependencies:** | component-D:v0.21.0 | +| **Impacted Dependency:** | component-D:v0.21.0 | +| **Fixed Versions:** | [0.24.3] | +| **CVSS V3:** | - | +### 🔬 JFrog Research Details **Remediation:** some remediation +
-
- -
- github.com/mholt/archiver/v3 v3.5.1 -
- +
github.com/mholt/archiver/v3 v3.5.1 -**Description:** -Summary +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Undetermined | +| **Direct Dependencies:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Impacted Dependency:** | github.com/mholt/archiver/v3:v3.5.1 | +| **Fixed Versions:** | - | +| **CVSS V3:** | - | -
+Summary
\ No newline at end of file diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index 703318b02..00da1b9ee 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -1,8 +1,6 @@ package issues import ( - // "github.com/jfrog/gofrog/datastructures" - "fmt" "maps" "github.com/jfrog/jfrog-cli-security/utils" @@ -259,7 +257,7 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl ScannerDescription: applicableInfo.ScannerDescription, IssueId: results.GetIssueIdentifier(issue.Cves, issue.IssueId, ","), CveSummary: issue.Summary, - ImpactedDependency: fmt.Sprintf("%s:%s", issue.ImpactedDependencyName, issue.ImpactedDependencyVersion), + ImpactedDependency: results.GetDependencyId(issue.ImpactedDependencyName, issue.ImpactedDependencyVersion), Remediation: remediation, }) } diff --git a/utils/outputwriter/icons.go b/utils/outputwriter/icons.go index 40426bae3..fcc4328cf 100644 --- a/utils/outputwriter/icons.go +++ b/utils/outputwriter/icons.go @@ -9,8 +9,8 @@ type ImageSource string type IconName string const ( - // baseResourceUrl = "https://raw.githubusercontent.com/jfrog/frogbot/master/resources/" - baseResourceUrl = "https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/" + baseResourceUrl = "https://raw.githubusercontent.com/jfrog/frogbot/master/resources/" + // baseResourceUrl = "https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/" NoVulnerabilityPrBannerSource ImageSource = "v2/noVulnerabilityBannerPR.png" NoVulnerabilityMrBannerSource ImageSource = "v2/noVulnerabilityBannerMR.png" @@ -78,15 +78,15 @@ func getApplicableIconTags(iconName IconName) string { func getSmallApplicableIconTags(iconName IconName) string { switch strings.ToLower(string(iconName)) { case "critical": - return GetImgTag(smallCriticalSeveritySource, "critical") + return GetImgTag(smallCriticalSeveritySource, "") case "high": - return GetImgTag(smallHighSeveritySource, "high") + return GetImgTag(smallHighSeveritySource, "") case "medium": - return GetImgTag(smallMediumSeveritySource, "medium") + return GetImgTag(smallMediumSeveritySource, "") case "low": - return GetImgTag(smallLowSeveritySource, "low") + return GetImgTag(smallLowSeveritySource, "") } - return GetImgTag(smallUnknownSeveritySource, "unknown") + return GetImgTag(smallUnknownSeveritySource, "") } func GetBanner(banner ImageSource) string { diff --git a/utils/outputwriter/icons_test.go b/utils/outputwriter/icons_test.go index 720ff4130..51434832a 100644 --- a/utils/outputwriter/icons_test.go +++ b/utils/outputwriter/icons_test.go @@ -6,20 +6,28 @@ import ( "github.com/stretchr/testify/assert" ) +func TestGetSmallSeverityTag(t *testing.T) { + assert.Equal(t, "\"\"/", getSmallSeverityTag("Critical")) + assert.Equal(t, "\"\"/", getSmallSeverityTag("HiGh")) + assert.Equal(t, "\"\"/", getSmallSeverityTag("meDium")) + assert.Equal(t, "\"\"/", getSmallSeverityTag("low")) + assert.Equal(t, "\"\"/", getSmallSeverityTag("none")) +} + func TestGetSeverityTag(t *testing.T) { - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
", getSeverityTag("Critical", "Undetermined")) - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
", getSeverityTag("HiGh", "Undetermined")) - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
", getSeverityTag("meDium", "Undetermined")) - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
", getSeverityTag("low", "Applicable")) - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableUnknownSeverity.png)
", getSeverityTag("none", "Applicable")) + assert.Equal(t, "![critical](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
", getSeverityTag("Critical", "Undetermined")) + assert.Equal(t, "![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
", getSeverityTag("HiGh", "Undetermined")) + assert.Equal(t, "![medium](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
", getSeverityTag("meDium", "Undetermined")) + assert.Equal(t, "![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
", getSeverityTag("low", "Applicable")) + assert.Equal(t, "![unknown](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableUnknownSeverity.png)
", getSeverityTag("none", "Applicable")) } func TestGetSeverityTagNotApplicable(t *testing.T) { - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
", getSeverityTag("Critical", "Not Applicable")) - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableHigh.png)
", getSeverityTag("HiGh", "Not Applicable")) - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableMedium.png)
", getSeverityTag("meDium", "Not Applicable")) - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableLow.png)
", getSeverityTag("low", "Not Applicable")) - assert.Equal(t, "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableUnknown.png)
", getSeverityTag("none", "Not Applicable")) + assert.Equal(t, "![critical (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
", getSeverityTag("Critical", "Not Applicable")) + assert.Equal(t, "![high (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableHigh.png)
", getSeverityTag("HiGh", "Not Applicable")) + assert.Equal(t, "![medium (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableMedium.png)
", getSeverityTag("meDium", "Not Applicable")) + assert.Equal(t, "![low (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableLow.png)
", getSeverityTag("low", "Not Applicable")) + assert.Equal(t, "![unknown (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableUnknown.png)
", getSeverityTag("none", "Not Applicable")) } func TestGetVulnerabilitiesBanners(t *testing.T) { diff --git a/utils/outputwriter/markdowntable_test.go b/utils/outputwriter/markdowntable_test.go index 3d08263bc..8087150f3 100644 --- a/utils/outputwriter/markdowntable_test.go +++ b/utils/outputwriter/markdowntable_test.go @@ -97,7 +97,7 @@ func TestMarkdownTableBuild(t *testing.T) { name: "No rows", columns: []string{"col1"}, rows: [][]string{}, - expectedOutput: "| col1 |\n" + defaultFirstColumnSeparator, + expectedOutput: "| col1 |\n" + centeredFirstColumnSeparator, }, { name: "Same number of columns", @@ -107,7 +107,7 @@ func TestMarkdownTableBuild(t *testing.T) { {"row2col1", "row2col2"}, {"row3col1", "row3col2"}, }, - expectedOutput: "| col1 | col2 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + ` + expectedOutput: "| col1 | col2 |\n" + centeredFirstColumnSeparator + centeredColumnSeparator + ` | row1col1 | row1col2 | | row2col1 | row2col2 | | row3col1 | row3col2 |`, @@ -123,7 +123,7 @@ func TestMarkdownTableBuild(t *testing.T) { {"row4col1"}, {"row5col1", "row5col2", "row5col3"}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + centeredFirstColumnSeparator + centeredColumnSeparator + centeredColumnSeparator + ` | row1col1 | row1col2 | - | | row2col1 | row2col2 | - | | row3col1 | - | row3col3 | @@ -147,7 +147,7 @@ func TestHideEmptyColumnsInTable(t *testing.T) { columns := []*MarkdownColumn{ {Name: "col1", HideIfAllEmpty: true}, {Name: "col2", HideIfAllEmpty: true, Centered: true}, - {Name: "col3", HideIfAllEmpty: false}, + {Name: "col3", HideIfAllEmpty: false, DefaultValue: "-"}, {Name: "col4", HideIfAllEmpty: true}, } testCases := []struct { @@ -161,7 +161,7 @@ func TestHideEmptyColumnsInTable(t *testing.T) { {"row1col1", "row1col2", "", "row1col4"}, {"row2col1", "row2col2", "", "row2col4"}, }, - expectedOutput: "| col1 | col2 | col3 | col4 |\n" + defaultFirstColumnSeparator + centeredColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 | col4 |\n" + defaultFirstColumnSeparator + centeredColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` | row1col1 | row1col2 | - | row1col4 | | row2col1 | row2col2 | - | row2col4 |`, }, @@ -181,8 +181,9 @@ func TestHideEmptyColumnsInTable(t *testing.T) { {"", "", "row1col3", ""}, {"", "", "", ""}, }, - expectedOutput: "| col3 |\n" + defaultFirstColumnSeparator + ` -| row1col3 |`, + expectedOutput: "| col3 |\n" + defaultFirstColumnSeparator + ` +| row1col3 | +| - |`, }, } for _, tc := range testCases { @@ -209,7 +210,7 @@ func TestMultipleValuesInColumnRow(t *testing.T) { rows: [][]CellData{ {{""}, {"row1col2"}, {"row1col3"}}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + centeredFirstColumnSeparator + centeredColumnSeparator + centeredColumnSeparator + ` | - | row1col2 | row1col3 |`, }, { @@ -219,7 +220,7 @@ func TestMultipleValuesInColumnRow(t *testing.T) { {{"row1col1"}, {"row1col2"}, {"row1col3"}}, {{"row2col1"}, {"row2col2"}, {"row2col3"}}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + centeredFirstColumnSeparator + centeredColumnSeparator + centeredColumnSeparator + ` | row1col1 | row1col2 | row1col3 | | row2col1 | row2col2 | row2col3 |`, }, @@ -231,7 +232,7 @@ func TestMultipleValuesInColumnRow(t *testing.T) { {{"row2col1"}, {"row2col2"}, {"row2col3val1", "row2col3val2"}}, {{"row3col1"}, {"row3col2val1", "row3col2val2", "row3col2val3"}, {"row3col3"}}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + centeredFirstColumnSeparator + centeredColumnSeparator + centeredColumnSeparator + ` | row1col1 | - | row1col3 | | row2col1 | row2col2 | row2col3val1, row2col3val2 | | row3col1 | row3col2val1, row3col2val2, row3col2val3 | row3col3 |`, @@ -244,7 +245,7 @@ func TestMultipleValuesInColumnRow(t *testing.T) { {{"row2col1"}, {"row2col2"}, {"row2col3val1", "row2col3val2"}}, {{"row3col1"}, {"row3col2val1", "row3col2val2", "row3col2val3"}, {"row3col3"}}, }, - expectedOutput: "| col1 | col2 | col3 |\n" + defaultFirstColumnSeparator + defaultColumnSeparator + defaultColumnSeparator + ` + expectedOutput: "| col1 | col2 | col3 |\n" + centeredFirstColumnSeparator + centeredColumnSeparator + centeredColumnSeparator + ` | row1col1 | - | row1col3 | | row2col1 | row2col2 | row2col3val1, row2col3val2 | | row3col1 | row3col2val1 | row3col3 | diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 4f683b3aa..40efb186c 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -150,7 +150,7 @@ func untitledForJasMsg(writer OutputWriter) string { if writer.AvoidExtraMessages() || writer.IsEntitledForJas() { return "" } - return writer.MarkAsDetails("Note:", 0, fmt.Sprintf("%s\n%s", SectionDivider(), writer.MarkInCenter(jasFeaturesMsgWhenNotEnabled))) + return writer.MarkAsDetails("Note", 0, fmt.Sprintf("\n%s\n%s", SectionDivider(), writer.MarkInCenter(jasFeaturesMsgWhenNotEnabled))) } func footer(writer OutputWriter) string { @@ -345,7 +345,7 @@ func getSecurityViolationsSummaryTable(violations []formats.VulnerabilityOrViola } row = append(row, getDirectDependenciesCellData(violation.Components), - NewCellData(fmt.Sprintf("%s:%s", violation.ImpactedDependencyName, violation.ImpactedDependencyVersion)), + NewCellData(results.GetDependencyId(violation.ImpactedDependencyName, violation.ImpactedDependencyVersion)), NewCellData(violation.Watch), ) table.AddRowWithCellData(row...) @@ -387,7 +387,7 @@ func getLicenseViolationsSummaryTable(licenses []formats.LicenseViolationRow, wr NewCellData(writer.FormattedSeverity(license.Severity, "Applicable")), NewCellData(license.LicenseKey), getDirectDependenciesCellData(license.Components), - NewCellData(fmt.Sprintf("%s:%s", license.ImpactedDependencyName, license.ImpactedDependencyVersion)), + NewCellData(results.GetDependencyId(license.ImpactedDependencyName, license.ImpactedDependencyVersion)), NewCellData(license.Watch), ) } @@ -425,14 +425,14 @@ func getScaLicenseViolationDetails(violation formats.LicenseViolationRow, writer // Details Table directComponent := []string{} for _, component := range violation.ImpactedDependencyDetails.Components { - directComponent = append(directComponent, fmt.Sprintf("%s:%s", component.Name, component.Version)) + directComponent = append(directComponent, results.GetDependencyId(component.Name, component.Version)) } noHeaderTable := NewNoHeaderMarkdownTable(2, false) noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Policies:")), NewCellData(violation.Policies...)) noHeaderTable.AddRow(MarkAsBold("Watch Name:"), violation.Watch) noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Direct Dependencies:")), NewCellData(directComponent...)) - noHeaderTable.AddRow(MarkAsBold("Impacted Dependency:"), fmt.Sprintf("%s:%s", violation.ImpactedDependencyName, violation.ImpactedDependencyVersion)) + noHeaderTable.AddRow(MarkAsBold("Impacted Dependency:"), results.GetDependencyId(violation.ImpactedDependencyName, violation.ImpactedDependencyVersion)) noHeaderTable.AddRow(MarkAsBold("Full Name:"), violation.LicenseName) WriteContent(&contentBuilder, noHeaderTable.Build(), "\n") @@ -601,7 +601,7 @@ func getDirectDependenciesCellData(components []formats.ComponentRow) (dependenc return NewCellData() } for _, component := range components { - dependencies = append(dependencies, fmt.Sprintf("%s:%s", component.Name, component.Version)) + dependencies = append(dependencies, results.GetDependencyId(component.Name, component.Version)) } return } @@ -669,7 +669,7 @@ func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, viola // Details Table directComponent := []string{} for _, component := range issue.ImpactedDependencyDetails.Components { - directComponent = append(directComponent, fmt.Sprintf("%s:%s", component.Name, component.Version)) + directComponent = append(directComponent, results.GetDependencyId(component.Name, component.Version)) } noHeaderTable := NewNoHeaderMarkdownTable(2, false) if len(issue.Policies) > 0 { @@ -686,7 +686,7 @@ func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, viola noHeaderTable.AddRow(MarkAsBold("Contextual Analysis:"), issue.Applicable) } noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Direct Dependencies:")), NewCellData(directComponent...)) - noHeaderTable.AddRow(MarkAsBold("Impacted Dependency:"), fmt.Sprintf("%s:%s", issue.ImpactedDependencyName, issue.ImpactedDependencyVersion)) + noHeaderTable.AddRow(MarkAsBold("Impacted Dependency:"), results.GetDependencyId(issue.ImpactedDependencyName, issue.ImpactedDependencyVersion)) noHeaderTable.AddRowWithCellData(NewCellData(MarkAsBold("Fixed Versions:")), NewCellData(issue.FixedVersions...)) cvss := []string{} diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index 34b3976d7..f56954c21 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -1,6 +1,7 @@ package outputwriter import ( + "fmt" "path/filepath" "testing" @@ -323,7 +324,42 @@ func TestScanSummaryContent(t *testing.T) { } func TestGetFrogbotErrorCommentContent(t *testing.T) { - + testCases := []struct { + name string + cases []OutputTestCase + issues issues.ScansIssuesCollection + }{ + { + name: "error details", + issues: issues.ScansIssuesCollection{ + ScanStatus: formats.ScanStatus{ + IacStatusCode: utils.NewIntPtr(33), + }, + }, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "structure", "error_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "structure", "error_simplified.md")}, + }, + }, + }, + } + for _, tc := range testCases { + for _, test := range tc.cases { + t.Run(tc.name+"_"+test.name, func(t *testing.T) { + expectedOutput := GetExpectedTestOutput(t, test) + output := GetFrogbotErrorCommentContent([]string{ScanSummaryContent(tc.issues, "", false, test.writer)}, fmt.Errorf("Some error that occurred in scan"), test.writer) + assert.Len(t, output, 1) + assert.Equal(t, expectedOutput, output[0]) + }) + } + } } func TestVulnerabilitiesContent(t *testing.T) { @@ -425,17 +461,17 @@ func TestVulnerabilitiesContent(t *testing.T) { cases: []OutputTestCase{ { name: "Standard output", - writer: &StandardOutput{MarkdownOutput{showCaColumn: true}}, + writer: &StandardOutput{MarkdownOutput{showCaColumn: true, hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard.md")}, }, { name: "Simplified output", - writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true}}, + writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true, hasInternetConnection: true}}, expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified.md")}, }, { name: "Split Standard output", - writer: &StandardOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1720, commentSizeLimit: 1720}}, + writer: &StandardOutput{MarkdownOutput{showCaColumn: true, hasInternetConnection: true, descriptionSizeLimit: 1720, commentSizeLimit: 1720}}, expectedOutputPath: []string{ filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split1.md"), filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_standard_split2.md"), @@ -443,7 +479,7 @@ func TestVulnerabilitiesContent(t *testing.T) { }, { name: "Split Simplified output", - writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true, descriptionSizeLimit: 1000, commentSizeLimit: 1000}}, + writer: &SimplifiedOutput{MarkdownOutput{showCaColumn: true, hasInternetConnection: true, descriptionSizeLimit: 1000, commentSizeLimit: 2000}}, expectedOutputPath: []string{ filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split1.md"), filepath.Join(testSummaryCommentDir, "vulnerabilities", "vulnerabilities_simplified_split2.md"), @@ -550,7 +586,7 @@ func getTestScaIssues(violations bool) []formats.VulnerabilityOrViolationRow { JfrogResearchInformation: &formats.JfrogResearchInformation{ Remediation: "some remediation", }, - Cves: []formats.CveRow{{}}, + Cves: []formats.CveRow{}, }, { ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ @@ -615,12 +651,12 @@ func TestLicensesContent(t *testing.T) { { name: "Standard output", writer: &StandardOutput{}, - expectedOutput: []string{""}, + expectedOutput: []string{}, }, { name: "Simplified output", writer: &SimplifiedOutput{}, - expectedOutput: []string{""}, + expectedOutput: []string{}, }, }, }, @@ -629,7 +665,8 @@ func TestLicensesContent(t *testing.T) { licenses: []formats.LicenseViolationRow{ { LicenseRow: formats.LicenseRow{ - LicenseKey: "License1", + LicenseKey: "License1", + LicenseName: "License1 full name", ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ Components: []formats.ComponentRow{{Name: "Comp1", Version: "1.0"}}, ImpactedDependencyName: "Dep1", @@ -639,6 +676,10 @@ func TestLicensesContent(t *testing.T) { }, }, }, + ViolationContext: formats.ViolationContext{ + Watch: "watch", + Policies: []string{"policy1", "policy2"}, + }, }, { LicenseRow: formats.LicenseRow{ @@ -661,6 +702,10 @@ func TestLicensesContent(t *testing.T) { }, }, }, + ViolationContext: formats.ViolationContext{ + Watch: "watch2", + Policies: []string{"policy3"}, + }, }, }, cases: []OutputTestCase{ @@ -680,7 +725,8 @@ func TestLicensesContent(t *testing.T) { for _, tc := range testCases { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { - assert.Equal(t, GetExpectedTestOutput(t, test), getLicenseViolationsContent(issues.ScansIssuesCollection{LicensesViolations: tc.licenses}, test.writer)) + expectedOutput := GetExpectedTestCaseOutput(t, test) + assert.Equal(t, expectedOutput, PolicyViolationsContent(issues.ScansIssuesCollection{LicensesViolations: tc.licenses}, test.writer)) }) } } diff --git a/utils/outputwriter/simplifiedoutput_test.go b/utils/outputwriter/simplifiedoutput_test.go index 6dc860cfb..5642dbb9d 100644 --- a/utils/outputwriter/simplifiedoutput_test.go +++ b/utils/outputwriter/simplifiedoutput_test.go @@ -126,11 +126,11 @@ func TestSimpleMarkAsDetails(t *testing.T) { subTitleDepth int }{ { - name: "empty", - summary: "", + name: "inline", + summary: "title", subTitleDepth: 0, - content: "", - expectedOutput: "\n---\n\n\n---\n", + content: "details", + expectedOutput: "title: details", }, { name: "empty content", @@ -184,10 +184,10 @@ func TestSimpleMarkAsTitle(t *testing.T) { subTitleDepth int }{ { - name: "empty", - title: "", + name: "inline", + title: "title", subTitleDepth: 0, - expectedOutput: "\n---\n\n\n---", + expectedOutput: "title", }, { name: "Main title", From 53e1ef8ed92ce545d6c8876ce609ec22650defa8 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 10 Dec 2024 18:21:07 +0200 Subject: [PATCH 18/34] done utils tests --- utils/issues/issuescollection.go | 131 +++++++++++++++------- utils/issues/issuescollection_test.go | 4 +- utils/outputwriter/standardoutput_test.go | 33 ++++-- 3 files changed, 119 insertions(+), 49 deletions(-) diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index 00da1b9ee..948cd766e 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -1,8 +1,6 @@ package issues import ( - "maps" - "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/jasutils" @@ -209,60 +207,117 @@ type ApplicableEvidences struct { Severity, ScannerDescription, IssueId, CveSummary, ImpactedDependency, Remediation string } +func toApplicableEvidences(issue formats.VulnerabilityOrViolationRow, cve formats.CveRow, evidence formats.Evidence) ApplicableEvidences { + remediation := "" + if issue.JfrogResearchInformation != nil { + remediation = issue.JfrogResearchInformation.Remediation + } + return ApplicableEvidences{ + Evidence: evidence, + Severity: issue.Severity, + ScannerDescription: cve.Applicability.ScannerDescription, + IssueId: results.GetIssueIdentifier(issue.Cves, issue.IssueId, ", "), + CveSummary: issue.Summary, + ImpactedDependency: results.GetDependencyId(issue.ImpactedDependencyName, issue.ImpactedDependencyVersion), + Remediation: remediation, + } +} + func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []ApplicableEvidences) { - issueIdToApplicableInfo := map[string]formats.Applicability{} - issueIdToIssue := map[string]formats.VulnerabilityOrViolationRow{} // Collect evidences from Violations + idToEvidence := map[string]ApplicableEvidences{} for _, securityViolation := range ic.ScaViolations { - issueId := results.GetIssueIdentifier(securityViolation.Cves, securityViolation.IssueId, "-") - if _, exists := issueIdToIssue[issueId]; exists { - // No need to add the same issue twice - continue - } for _, cve := range securityViolation.Cves { if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { // We only want applicable issues - issueIdToIssue[issueId] = securityViolation - issueIdToApplicableInfo[issueId] = *cve.Applicability + for _, evidence := range cve.Applicability.Evidence { + issueId := results.GetIssueIdentifier(securityViolation.Cves, securityViolation.IssueId, "-") + id := issueId + evidence.Location.ToString() + if _, exists := idToEvidence[id]; exists { + // No need to add the same issue twice + continue + } + idToEvidence[id] = toApplicableEvidences(securityViolation, cve, evidence) + } } } } // Collect evidences from Vulnerabilities for _, vulnerability := range ic.ScaVulnerabilities { - issueId := results.GetIssueIdentifier(vulnerability.Cves, vulnerability.IssueId, "-") - if _, exists := issueIdToIssue[issueId]; exists { - // No need to add the same issue twice - continue - } for _, cve := range vulnerability.Cves { if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { // We only want applicable issues - issueIdToIssue[issueId] = vulnerability - issueIdToApplicableInfo[issueId] = *cve.Applicability + for _, evidence := range cve.Applicability.Evidence { + issueId := results.GetIssueIdentifier(vulnerability.Cves, vulnerability.IssueId, "-") + id := issueId + evidence.Location.ToString() + if _, exists := idToEvidence[id]; exists { + // No need to add the same issue twice + continue + } + idToEvidence[id] = toApplicableEvidences(vulnerability, cve, evidence) + } } } } - // Create ApplicableEvidences from collected data - for issueId := range maps.Keys(issueIdToApplicableInfo) { - issue := issueIdToIssue[issueId] - applicableInfo := issueIdToApplicableInfo[issueId] - remediation := "" - if issue.JfrogResearchInformation != nil { - remediation = issue.JfrogResearchInformation.Remediation - } - for _, evidence := range applicableInfo.Evidence { - evidences = append(evidences, ApplicableEvidences{ - Evidence: evidence, - Severity: issue.Severity, - ScannerDescription: applicableInfo.ScannerDescription, - IssueId: results.GetIssueIdentifier(issue.Cves, issue.IssueId, ","), - CveSummary: issue.Summary, - ImpactedDependency: results.GetDependencyId(issue.ImpactedDependencyName, issue.ImpactedDependencyVersion), - Remediation: remediation, - }) - } + + for _, evidence := range idToEvidence { + evidences = append(evidences, evidence) } - return + return + + // issueIdToApplicableInfo := map[string]formats.Applicability{} + // issueIdToIssue := map[string]formats.VulnerabilityOrViolationRow{} + // // Collect evidences from Violations + // for _, securityViolation := range ic.ScaViolations { + // issueId := results.GetIssueIdentifier(securityViolation.Cves, securityViolation.IssueId, "-") + // if _, exists := issueIdToIssue[issueId]; exists { + // // No need to add the same issue twice + // continue + // } + // for _, cve := range securityViolation.Cves { + // if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { + // // We only want applicable issues + // issueIdToIssue[issueId] = securityViolation + // issueIdToApplicableInfo[issueId] = *cve.Applicability + // } + // } + // } + // // Collect evidences from Vulnerabilities + // for _, vulnerability := range ic.ScaVulnerabilities { + // issueId := results.GetIssueIdentifier(vulnerability.Cves, vulnerability.IssueId, "-") + // if _, exists := issueIdToIssue[issueId]; exists { + // // No need to add the same issue twice + // continue + // } + // for _, cve := range vulnerability.Cves { + // if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { + // // We only want applicable issues + // issueIdToIssue[issueId] = vulnerability + // issueIdToApplicableInfo[issueId] = *cve.Applicability + // } + // } + // } + // // Create ApplicableEvidences from collected data + // for issueId := range maps.Keys(issueIdToApplicableInfo) { + // issue := issueIdToIssue[issueId] + // applicableInfo := issueIdToApplicableInfo[issueId] + // remediation := "" + // if issue.JfrogResearchInformation != nil { + // remediation = issue.JfrogResearchInformation.Remediation + // } + // for _, evidence := range applicableInfo.Evidence { + // evidences = append(evidences, ApplicableEvidences{ + // Evidence: evidence, + // Severity: issue.Severity, + // ScannerDescription: applicableInfo.ScannerDescription, + // IssueId: results.GetIssueIdentifier(issue.Cves, issue.IssueId, ","), + // CveSummary: issue.Summary, + // ImpactedDependency: results.GetDependencyId(issue.ImpactedDependencyName, issue.ImpactedDependencyVersion), + // Remediation: remediation, + // }) + // } + // } + // return } // Violations diff --git a/utils/issues/issuescollection_test.go b/utils/issues/issuescollection_test.go index 8c47aa38c..8ea2aee98 100644 --- a/utils/issues/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -312,11 +312,11 @@ func TestGetApplicableEvidences(t *testing.T) { expectedEvidences: []ApplicableEvidences{ { Evidence: formats.Evidence{Reason: "reason", Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "snippet1"}}, - Severity: "High", ScannerDescription: "scanner", IssueId: "Xray-Id", CveSummary: "summary", ImpactedDependency: "impacted-name:1.0.0", Remediation: "remediation", + Severity: "Critical", ScannerDescription: "scanner", IssueId: "CVE-2021-1234", CveSummary: "summary", ImpactedDependency: "impacted-name:1.0.0", Remediation: "remediation", }, { Evidence: formats.Evidence{Reason: "other reason", Location: formats.Location{File: "file2", StartLine: 5, StartColumn: 6, EndLine: 7, EndColumn: 8, Snippet: "snippet2"}}, - Severity: "High", ScannerDescription: "scanner", IssueId: "Xray-Id", CveSummary: "summary", ImpactedDependency: "impacted-name:1.0.0", Remediation: "remediation", + Severity: "High", ScannerDescription: "scanner", IssueId: "CVE-2021-1234", CveSummary: "summary", ImpactedDependency: "impacted-name:1.0.0", Remediation: "remediation", }, }, }, diff --git a/utils/outputwriter/standardoutput_test.go b/utils/outputwriter/standardoutput_test.go index 133c01905..3f0c68de0 100644 --- a/utils/outputwriter/standardoutput_test.go +++ b/utils/outputwriter/standardoutput_test.go @@ -56,23 +56,38 @@ func TestStandardFormattedSeverity(t *testing.T) { severity string applicability string expectedOutput string + internetConnection bool }{ { name: "Applicable severity", severity: "Low", applicability: "Applicable", - expectedOutput: "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low", + internetConnection: true, + expectedOutput: "![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low", }, { name: "Not applicable severity", severity: "Medium", applicability: "Not Applicable", - expectedOutput: "![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableMedium.png)
Medium", + internetConnection: true, + expectedOutput: "![medium (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableMedium.png)
Medium", + }, + { + name: "Applicable severity", + severity: "Low", + applicability: "Applicable", + expectedOutput: "Low", + }, + { + name: "Not applicable severity", + severity: "Medium", + applicability: "Not Applicable", + expectedOutput: "Medium", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - smo := &StandardOutput{} + smo := &StandardOutput{MarkdownOutput: MarkdownOutput{hasInternetConnection: tc.internetConnection}} assert.Equal(t, tc.expectedOutput, smo.FormattedSeverity(tc.severity, tc.applicability)) }) } @@ -161,42 +176,42 @@ func TestStandardMarkAsDetails(t *testing.T) { summary: "", subTitleDepth: 0, content: "", - expectedOutput: "
\n\n\n\n
\n", + expectedOutput: "

", }, { name: "empty content", summary: "summary", subTitleDepth: 1, content: "", - expectedOutput: "
\n summary \n
\n\n\n\n
\n", + expectedOutput: "
summary
", }, { name: "empty summary", summary: "", subTitleDepth: 0, content: "content", - expectedOutput: "
\n\ncontent\n\n
\n", + expectedOutput: "
content
", }, { name: "Main details", summary: "summary", subTitleDepth: 1, content: "content", - expectedOutput: "
\n summary \n
\n\ncontent\n\n
\n", + expectedOutput: "
summarycontent
", }, { name: "Sub details", summary: "summary", subTitleDepth: 2, content: "content", - expectedOutput: "
\n summary \n
\n\ncontent\n\n
\n", + expectedOutput: "
summarycontent
", }, { name: "Sub sub details", summary: "summary", subTitleDepth: 3, content: "content", - expectedOutput: "
\n summary \n
\n\ncontent\n\n
\n", + expectedOutput: "
summarycontent
", }, } for _, tc := range testCases { From d37e974066cba63691659dffe958e50d31516ae0 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 10 Dec 2024 22:59:10 +0200 Subject: [PATCH 19/34] done fix tests --- scanpullrequest/scanpullrequest_test.go | 30 ++++++++++ scanrepository/scanrepository.go | 20 +++---- .../test_proj_pip_with_vulnerability.md | 37 ++++++++++-- ...test_proj_with_vulnerability_simplified.md | 45 ++++++++++++-- .../test_proj_with_vulnerability_standard.md | 37 ++++++++++-- testdata/scanpullrequest/expected_response.md | 37 ++++++++++-- .../expected_response_multi_dir.md | 60 +++++++++++++------ utils/issues/issuescollection.go | 4 +- utils/outputwriter/standardoutput_test.go | 24 ++++---- utils/scandetails.go | 3 +- 10 files changed, 232 insertions(+), 65 deletions(-) diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index 539bd80e6..ee55f8d36 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -515,6 +515,9 @@ func TestGetAllIssues(t *testing.T) { Severity: "High", SeverityNumValue: 21, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "Missing auto upgrade was detected", Location: formats.Location{ File: "file1", @@ -532,6 +535,9 @@ func TestGetAllIssues(t *testing.T) { Severity: "High", SeverityNumValue: 21, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "Secret", Location: formats.Location{ File: "index.js", @@ -549,6 +555,9 @@ func TestGetAllIssues(t *testing.T) { Severity: "High", SeverityNumValue: 21, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "XSS Vulnerability", Location: formats.Location{ File: "file1", @@ -572,6 +581,9 @@ func TestGetAllIssues(t *testing.T) { ImpactedDependencyName: "Dep-1", }, }, + ViolationContext: formats.ViolationContext{ + Watch: "jfrog_custom_license_violation", + }, }, }, } @@ -865,6 +877,9 @@ func TestCreateNewIacRows(t *testing.T) { Severity: "High", SeverityNumValue: 21, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "Missing auto upgrade was detected", Location: formats.Location{ File: "file1", @@ -895,6 +910,9 @@ func TestCreateNewIacRows(t *testing.T) { Severity: "Medium", SeverityNumValue: 17, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "enable_private_endpoint=false was detected", Location: formats.Location{ File: "file2", @@ -948,6 +966,9 @@ func TestCreateNewSecretRows(t *testing.T) { Severity: "High", SeverityNumValue: 21, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "Secret", Location: formats.Location{ File: "file1", @@ -978,6 +999,9 @@ func TestCreateNewSecretRows(t *testing.T) { Severity: "Medium", SeverityNumValue: 17, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "Secret", Location: formats.Location{ File: "file2", @@ -1031,6 +1055,9 @@ func TestCreateNewSastRows(t *testing.T) { Severity: "High", SeverityNumValue: 21, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "XSS Vulnerability", Location: formats.Location{ File: "file1", @@ -1061,6 +1088,9 @@ func TestCreateNewSastRows(t *testing.T) { Severity: "Medium", SeverityNumValue: 17, }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule", + }, Finding: "Stack Trace Exposure", Location: formats.Location{ File: "file2", diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index 076e14989..d387e7022 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -122,26 +122,24 @@ func (cfp *ScanRepositoryCmd) scanAndFixBranch(repository *utils.Repository) (er } func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Repository, client vcsclient.VcsClient) (err error) { + // Set the scan details + cfp.scanDetails = utils.NewScanDetails(client, &repository.Server, &repository.Git). + SetFailOnInstallationErrors(*repository.FailOnSecurityIssues). + SetFixableOnly(repository.FixableOnly). + SetSkipAutoInstall(repository.SkipAutoInstall). + SetAllowPartialResults(repository.AllowPartialResults). + SetDisableJas(repository.DisableJas) + repositoryInfo, err := client.GetRepositoryInfo(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName) if err != nil { return } - gitRepoContextValue := "" if repository.ViolationContext == utils.GitRepoContext { // The violation context is the Git repository, inject the Git repository context to the scan details gitRepoContextValue = repositoryInfo.CloneInfo.HTTP } - - // Set the scan details - cfp.scanDetails = utils.NewScanDetails(client, &repository.Server, &repository.Git). - SetXrayGraphScanParams(gitRepoContextValue, repository.Watches, repository.JFrogProjectKey, len(repository.AllowedLicenses) > 0). - SetFailOnInstallationErrors(*repository.FailOnSecurityIssues). - SetFixableOnly(repository.FixableOnly). - SetSkipAutoInstall(repository.SkipAutoInstall). - SetAllowPartialResults(repository.AllowPartialResults). - SetDisableJas(repository.DisableJas) - + cfp.scanDetails.SetXrayGraphScanParams(gitRepoContextValue, repository.Watches, repository.JFrogProjectKey, len(repository.AllowedLicenses) > 0) cfp.scanDetails.XrayVersion = cfp.XrayVersion cfp.scanDetails.XscVersion = cfp.XscVersion diff --git a/testdata/messages/integration/test_proj_pip_with_vulnerability.md b/testdata/messages/integration/test_proj_pip_with_vulnerability.md index 240ce4a05..bbf3d31c8 100644 --- a/testdata/messages/integration/test_proj_pip_with_vulnerability.md +++ b/testdata/messages/integration/test_proj_pip_with_vulnerability.md @@ -9,21 +9,47 @@
-## 📦 Vulnerable Dependencies -### ✍️ Summary +## 📗 Scan Summary +- Frogbot scanned for Vulnerabilities and found 1 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done |
1 Issues Found 1 High
| +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done | Not Found | +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ✅ Done | Not Found | + +### 📦 Vulnerable Dependencies +
-| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | Not Covered | pip-example:1.2.3 | pyjwt 1.7.1 | [2.4.0] | CVE-2022-29217 | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | CVE-2022-29217 | Not Covered | pip-example:1.2.3 | pyjwt 1.7.1 | [2.4.0] |
-### 🔬 Research Details +### 🔖 Details + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Jfrog Research Severity:** | Medium | +| **Contextual Analysis:** | Not Covered | +| **Direct Dependencies:** | pip-example:1.2.3 | +| **Impacted Dependency:** | pyjwt:1.7.1 | +| **Fixed Versions:** | [2.4.0] | +| **CVSS V3:** | 7.5 | + +Algorithm confusion in PyJWT leads to authentication bypass. + +### 🔬 JFrog Research Details + **Description:** [PyJWT](https://pypi.org/project/PyJWT) is a Python implementation of the RFC 7519 standard (JSON Web Tokens). [JSON Web Tokens](https://jwt.io/) are an open, industry standard method for representing claims securely between two parties. A JWT comes with an inline signature that is meant to be verified by the receiving application. JWT supports multiple standard algorithms, and the algorithm itself is **specified in the JWT token itself**. @@ -63,6 +89,7 @@ With - `jwt.decode(encoded_jwt, pub_key_bytes, algorithms=["ES256"])` + ---
diff --git a/testdata/messages/integration/test_proj_with_vulnerability_simplified.md b/testdata/messages/integration/test_proj_with_vulnerability_simplified.md index e02a44fc2..06aff0274 100644 --- a/testdata/messages/integration/test_proj_with_vulnerability_simplified.md +++ b/testdata/messages/integration/test_proj_with_vulnerability_simplified.md @@ -5,26 +5,60 @@ **🚨 Frogbot scanned this pull request and found the below:** + --- -## 📦 Vulnerable Dependencies +## 📗 Scan Summary --- +- Frogbot scanned for Vulnerabilities and found 1 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done | 1 Issues Found: ❗️ 1 Critical | +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done | Not Found | +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ✅ Done | Not Found | --- -### ✍️ Summary +### 📦 Vulnerable Dependencies --- -| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | + +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| Critical | Not Applicable | minimist:1.2.5 | minimist 1.2.5 | [0.2.4], [1.2.6] | CVE-2021-44906 | +| Critical | CVE-2021-44906 | Not Applicable | minimist:1.2.5 | minimist 1.2.5 | [0.2.4], [1.2.6] | + + +--- +### 🔖 Details + +--- + + +--- +### Vulnerability Details + --- -### 🔬 Research Details +| | | +| --------------------- | :-----------------------------------: | +| **Jfrog Research Severity:** | 🔴 High | +| **Contextual Analysis:** | Not Applicable | +| **Direct Dependencies:** | minimist:1.2.5 | +| **Impacted Dependency:** | minimist:1.2.5 | +| **Fixed Versions:** | [0.2.4], [1.2.6] | +| **CVSS V3:** | 9.8 | + +Insufficient input validation in Minimist npm package leads to prototype pollution of constructor functions when parsing arbitrary arguments. + --- +### 🔬 JFrog Research Details +--- **Description:** [Minimist](https://github.com/substack/minimist) is a simple and very popular argument parser. It is used by more than 14 million by Mar 2022. This package developers stopped developing it since April 2020 and its community released a [newer version](https://github.com/meszaros-lajos-gyorgy/minimist-lite) supported by the community. @@ -46,5 +80,6 @@ This vulnerability can be triggered when the attacker-controlled input is parsed Add the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks. + --- [🐸 JFrog Frogbot](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) \ No newline at end of file diff --git a/testdata/messages/integration/test_proj_with_vulnerability_standard.md b/testdata/messages/integration/test_proj_with_vulnerability_standard.md index 9a9649763..64317f7fb 100644 --- a/testdata/messages/integration/test_proj_with_vulnerability_standard.md +++ b/testdata/messages/integration/test_proj_with_vulnerability_standard.md @@ -9,21 +9,47 @@
-## 📦 Vulnerable Dependencies -### ✍️ Summary +## 📗 Scan Summary +- Frogbot scanned for Vulnerabilities and found 1 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done |
1 Issues Found 1 Critical
| +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done | Not Found | +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ✅ Done | Not Found | + +### 📦 Vulnerable Dependencies +
-| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | Not Applicable | minimist:1.2.5 | minimist 1.2.5 | [0.2.4]
[1.2.6] | CVE-2021-44906 | +| ![critical (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-2021-44906 | Not Applicable | minimist:1.2.5 | minimist 1.2.5 | [0.2.4]
[1.2.6] |
-### 🔬 Research Details +### 🔖 Details + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Jfrog Research Severity:** | High | +| **Contextual Analysis:** | Not Applicable | +| **Direct Dependencies:** | minimist:1.2.5 | +| **Impacted Dependency:** | minimist:1.2.5 | +| **Fixed Versions:** | [0.2.4], [1.2.6] | +| **CVSS V3:** | 9.8 | + +Insufficient input validation in Minimist npm package leads to prototype pollution of constructor functions when parsing arbitrary arguments. + +### 🔬 JFrog Research Details + **Description:** [Minimist](https://github.com/substack/minimist) is a simple and very popular argument parser. It is used by more than 14 million by Mar 2022. This package developers stopped developing it since April 2020 and its community released a [newer version](https://github.com/meszaros-lajos-gyorgy/minimist-lite) supported by the community. @@ -44,6 +70,7 @@ This vulnerability can be triggered when the attacker-controlled input is parsed Add the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks. + ---
diff --git a/testdata/scanpullrequest/expected_response.md b/testdata/scanpullrequest/expected_response.md index 9a9649763..64317f7fb 100644 --- a/testdata/scanpullrequest/expected_response.md +++ b/testdata/scanpullrequest/expected_response.md @@ -9,21 +9,47 @@
-## 📦 Vulnerable Dependencies -### ✍️ Summary +## 📗 Scan Summary +- Frogbot scanned for Vulnerabilities and found 1 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done |
1 Issues Found 1 Critical
| +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done | Not Found | +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ✅ Done | Not Found | + +### 📦 Vulnerable Dependencies +
-| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | Not Applicable | minimist:1.2.5 | minimist 1.2.5 | [0.2.4]
[1.2.6] | CVE-2021-44906 | +| ![critical (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | CVE-2021-44906 | Not Applicable | minimist:1.2.5 | minimist 1.2.5 | [0.2.4]
[1.2.6] |
-### 🔬 Research Details +### 🔖 Details + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Jfrog Research Severity:** | High | +| **Contextual Analysis:** | Not Applicable | +| **Direct Dependencies:** | minimist:1.2.5 | +| **Impacted Dependency:** | minimist:1.2.5 | +| **Fixed Versions:** | [0.2.4], [1.2.6] | +| **CVSS V3:** | 9.8 | + +Insufficient input validation in Minimist npm package leads to prototype pollution of constructor functions when parsing arbitrary arguments. + +### 🔬 JFrog Research Details + **Description:** [Minimist](https://github.com/substack/minimist) is a simple and very popular argument parser. It is used by more than 14 million by Mar 2022. This package developers stopped developing it since April 2020 and its community released a [newer version](https://github.com/meszaros-lajos-gyorgy/minimist-lite) supported by the community. @@ -44,6 +70,7 @@ This vulnerability can be triggered when the attacker-controlled input is parsed Add the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks. + ---
diff --git a/testdata/scanpullrequest/expected_response_multi_dir.md b/testdata/scanpullrequest/expected_response_multi_dir.md index ae7d4a745..6e1dfd79a 100644 --- a/testdata/scanpullrequest/expected_response_multi_dir.md +++ b/testdata/scanpullrequest/expected_response_multi_dir.md @@ -9,35 +9,61 @@
-## 📦 Vulnerable Dependencies -### ✍️ Summary +## 📗 Scan Summary +- Frogbot scanned for Vulnerabilities and found 2 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done |
2 Issues Found 2 High
| +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done | Not Found | +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ✅ Done | Not Found | + +### 📦 Vulnerable Dependencies +
-| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES | +| Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableHigh.png)
High | Not Applicable | minimatch:3.0.4 | minimatch 3.0.4 | [3.0.5] | CVE-2022-3517 | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | Not Covered | pyjwt:1.7.1 | pyjwt 1.7.1 | [2.4.0] | CVE-2022-29217 | +| ![high (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableHigh.png)
High | CVE-2022-3517 | Not Applicable | minimatch:3.0.4 | minimatch 3.0.4 | [3.0.5] | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | CVE-2022-29217 | Not Covered | pyjwt:1.7.1 | pyjwt 1.7.1 | [2.4.0] |
-### 🔬 Research Details +### 🔖 Details -
- [ CVE-2022-3517 ] minimatch 3.0.4 -
+
[ CVE-2022-3517 ] minimatch 3.0.4 -**Description:** -A vulnerability was found in the minimatch package. This flaw allows a Regular Expression Denial of Service (ReDoS) when calling the braceExpand function with specific arguments, resulting in a Denial of Service. +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Not Applicable | +| **Direct Dependencies:** | minimatch:3.0.4 | +| **Impacted Dependency:** | minimatch:3.0.4 | +| **Fixed Versions:** | [3.0.5] | +| **CVSS V3:** | 7.5 | -
+A vulnerability was found in the minimatch package. This flaw allows a Regular Expression Denial of Service (ReDoS) when calling the braceExpand function with specific arguments, resulting in a Denial of Service.
-
- [ CVE-2022-29217 ] pyjwt 1.7.1 -
+
[ CVE-2022-29217 ] pyjwt 1.7.1 +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Jfrog Research Severity:** | Medium | +| **Contextual Analysis:** | Not Covered | +| **Direct Dependencies:** | pyjwt:1.7.1 | +| **Impacted Dependency:** | pyjwt:1.7.1 | +| **Fixed Versions:** | [2.4.0] | +| **CVSS V3:** | 7.5 | + +Algorithm confusion in PyJWT leads to authentication bypass. + +### 🔬 JFrog Research Details **Description:** [PyJWT](https://pypi.org/project/PyJWT) is a Python implementation of the RFC 7519 standard (JSON Web Tokens). [JSON Web Tokens](https://jwt.io/) are an open, industry standard method for representing claims securely between two parties. A JWT comes with an inline signature that is meant to be verified by the receiving application. JWT supports multiple standard algorithms, and the algorithm itself is **specified in the JWT token itself**. @@ -76,9 +102,7 @@ For example, replace the following call - `jwt.decode(encoded_jwt, pub_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())` With - `jwt.decode(encoded_jwt, pub_key_bytes, algorithms=["ES256"])` - -
- +
--- diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index 948cd766e..85296ebdc 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -259,11 +259,11 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl } } } - + for _, evidence := range idToEvidence { evidences = append(evidences, evidence) } - return + return // issueIdToApplicableInfo := map[string]formats.Applicability{} // issueIdToIssue := map[string]formats.VulnerabilityOrViolationRow{} diff --git a/utils/outputwriter/standardoutput_test.go b/utils/outputwriter/standardoutput_test.go index 3f0c68de0..f3f235901 100644 --- a/utils/outputwriter/standardoutput_test.go +++ b/utils/outputwriter/standardoutput_test.go @@ -52,25 +52,25 @@ func TestStandardSeparator(t *testing.T) { func TestStandardFormattedSeverity(t *testing.T) { testCases := []struct { - name string - severity string - applicability string - expectedOutput string + name string + severity string + applicability string + expectedOutput string internetConnection bool }{ { - name: "Applicable severity", - severity: "Low", - applicability: "Applicable", + name: "Applicable severity", + severity: "Low", + applicability: "Applicable", internetConnection: true, - expectedOutput: "![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low", + expectedOutput: "![low](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableLowSeverity.png)
Low", }, { - name: "Not applicable severity", - severity: "Medium", - applicability: "Not Applicable", + name: "Not applicable severity", + severity: "Medium", + applicability: "Not Applicable", internetConnection: true, - expectedOutput: "![medium (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableMedium.png)
Medium", + expectedOutput: "![medium (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableMedium.png)
Medium", }, { name: "Applicable severity", diff --git a/utils/scandetails.go b/utils/scandetails.go index 19e5a3d24..78a6cb565 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -204,8 +204,7 @@ func (sc *ScanDetails) RunInstallAndAudit(workDirs ...string) (auditResults *res SetAllowPartialResults(sc.allowPartialResults). SetExclusions(sc.PathExclusions). SetIsRecursiveScan(sc.IsRecursiveScan). - SetUseJas(!sc.DisableJas()). - SetScansToPerform([]utils.SubScanType{utils.ScaScan, utils.ContextualAnalysisScan, utils.SastScan, utils.SecretsScan, utils.SecretTokenValidationScan}) + SetUseJas(!sc.DisableJas()) auditParams := audit.NewAuditParams(). SetWorkingDirs(workDirs). From 3e8b0c64605f66c2a9464a8fe4768b91fa080268 Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 11 Dec 2024 09:06:33 +0200 Subject: [PATCH 20/34] start clean --- go.mod | 7 +- go.sum | 2 + scanpullrequest/scanpullrequest.go | 4 +- utils/comment.go | 42 ++------ utils/comment_test.go | 5 +- utils/issues/issuescollection.go | 120 ----------------------- utils/outputwriter/markdowntable.go | 6 -- utils/outputwriter/outputcontent.go | 10 +- utils/outputwriter/outputcontent_test.go | 2 +- 9 files changed, 19 insertions(+), 179 deletions(-) diff --git a/go.mod b/go.mod index 34a74fdcd..8bc18f2ff 100644 --- a/go.mod +++ b/go.mod @@ -121,11 +121,8 @@ require ( ) // eranturgeman:jas-violations-support -replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security - -// replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241204143029-e901cd468c75 - -// replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241124185605-a69b532152fc +// replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211070414-848d30bb3042 // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev diff --git a/go.sum b/go.sum index 60660ac4b..e60b7a09c 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211070414-848d30bb3042 h1:0jz+yrWt4B5oS1jGMU78qgywpOr+Is4cLkGCZjlzpQ8= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211070414-848d30bb3042/go.mod h1:c4GycFVqXqYOjI/1FdJ1lUf//kqQBGEzds1iSn73OaM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index e6a21fc35..4b7baf784 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -41,7 +41,7 @@ func (cmd *ScanPullRequestCmd) Run(configAggregator utils.RepoAggregator, client return } } - repoConfig.OutputWriter.SetHasInternetConnection( /*false*/ frogbotRepoConnection.IsConnected()) + repoConfig.OutputWriter.SetHasInternetConnection(frogbotRepoConnection.IsConnected()) if repoConfig.PullRequestDetails, err = client.GetPullRequestByID(context.Background(), repoConfig.RepoOwner, repoConfig.RepoName, int(repoConfig.PullRequestDetails.ID)); err != nil { return } @@ -310,7 +310,7 @@ func checkoutToCommitAtTempWorkingDir(scanDetails *utils.ScanDetails, commitHash } func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*issues.ScansIssuesCollection, error) { - log.Info("Frogbot is configured to show all vulnerabilities") + log.Info("Frogbot is configured to show all issues") simpleJsonResults, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{ IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext, diff --git a/utils/comment.go b/utils/comment.go index 3c8b01380..9fe183697 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -32,6 +32,7 @@ const ( commentRemovalErrorMsg = "An error occurred while attempting to remove older Frogbot pull request comments:" ) +// In Scan PR, if there is an error, a comment will be added to the PR with the error message. func HandlePullRequestErrorComment(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int, scanError error) (err error) { if issues == nil { log.Debug("Can't generate error comment without issues collection") @@ -46,6 +47,7 @@ func HandlePullRequestErrorComment(issues *issues.ScansIssuesCollection, repo *R return } +// In Scan PR, if there are no issues, comments will be added to the PR with a message that there are no issues. func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int) (err error) { if !repo.Params.AvoidPreviousPrCommentsDeletion { // The removal of comments may fail for various reasons, @@ -91,7 +93,7 @@ func DeleteExistingPullRequestComments(repository *Repository, client vcsclient. "failed to get comments. the following details were used in order to fetch the comments: <%s/%s> pull request #%d. the error received: %s", repository.RepoOwner, repository.RepoName, int(repository.PullRequestDetails.ID), err.Error()) } - commentsToDelete := getFrogbotComments(repository.OutputWriter, comments) + commentsToDelete := getFrogbotComments(comments) // Delete if len(commentsToDelete) > 0 { for _, commentToDelete := range commentsToDelete { @@ -178,7 +180,7 @@ func addReviewComments(repo *Repository, pullRequestID int, client vcsclient.Vcs log.Debug("creating a review comment for", comment.Type, comment.Location.File, comment.Location.StartLine, comment.Location.StartColumn) if e := client.AddPullRequestReviewComments(context.Background(), repo.RepoOwner, repo.RepoName, pullRequestID, comment.CommentInfo); e != nil { log.Debug("couldn't add pull request review comment, fallback to regular comment: " + e.Error()) - if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, outputwriter.GetFallbackReviewCommentContent(comment.CommentInfo.Content, comment.Location, repo.OutputWriter), pullRequestID); err != nil { + if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, outputwriter.GetFallbackReviewCommentContent(comment.CommentInfo.Content, comment.Location), pullRequestID); err != nil { err = errors.New("couldn't add pull request comment, fallback to comment: " + err.Error()) return } @@ -197,7 +199,7 @@ func DeleteExistingPullRequestReviewComments(repo *Repository, pullRequestID int } // Delete old review comments if len(existingComments) > 0 { - if err = client.DeletePullRequestReviewComments(context.Background(), repo.RepoOwner, repo.RepoName, pullRequestID, getFrogbotComments(repo.OutputWriter, existingComments)...); err != nil { + if err = client.DeletePullRequestReviewComments(context.Background(), repo.RepoOwner, repo.RepoName, pullRequestID, getFrogbotComments(existingComments)...); err != nil { err = errors.New("couldn't delete pull request review comment: " + err.Error()) return } @@ -205,7 +207,7 @@ func DeleteExistingPullRequestReviewComments(repo *Repository, pullRequestID int return } -func getFrogbotComments(writer outputwriter.OutputWriter, existingComments []vcsclient.CommentInfo) (reviewComments []vcsclient.CommentInfo) { +func getFrogbotComments(existingComments []vcsclient.CommentInfo) (reviewComments []vcsclient.CommentInfo) { for _, comment := range existingComments { if outputwriter.IsFrogbotComment(comment.Content) { log.Debug("Deleting comment id:", comment.ID) @@ -278,26 +280,6 @@ func groupSimilarJasIssues(issues []formats.SourceCodeRow) (groupedIssues []simi return } -// // We group issues by their watches, so we can add all the watches to the same comment. -// func groupSimilarIssues(issues []formats.SourceCodeRow) (groupedIssues []formats.SourceCodeRow, issuesWatches map[string][]formats.ViolationContext) { -// issuesWatches = make(map[string][]formats.ViolationContext) -// for _, issue := range issues { -// if issue.Watch == "" { -// // no violation context, just add to the list -// groupedIssues = append(groupedIssues, issue) -// continue -// } -// id := getSourceCodeRowId(issue) -// if watches, ok := issuesWatches[id]; ok { -// issuesWatches[id] = append(watches, issue.ViolationContext) -// continue -// } -// groupedIssues = append(groupedIssues, issue) -// issuesWatches[id] = []formats.ViolationContext{issue.ViolationContext} -// } -// return groupedIssues, issuesWatches -// } - // We show different comments for each location and rule ID. (we group similar issues/violations to the same comment) func getSourceCodeRowId(issue formats.SourceCodeRow) string { return issue.RuleId + issue.Location.ToString() @@ -321,18 +303,6 @@ func generateApplicabilityReviewContent(issue issues.ApplicableEvidences, writer return outputwriter.GenerateReviewCommentContent(outputwriter.ApplicableCveReviewContent(issue, writer), writer) } -// func generateSourceCodeVulnerabilityReviewContent(commentType ReviewCommentType, issue formats.SourceCodeRow, writer outputwriter.OutputWriter) (content string) { -// switch commentType { -// case IacComment: -// return outputwriter.GenerateReviewCommentContent(outputwriter.IacReviewContent(issue, writer), writer) -// case SastComment: -// return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(issue, writer), writer) -// case SecretComment: -// return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(issue, writer), writer) -// } -// return -// } - func generateSourceCodeReviewContent(commentType ReviewCommentType, violation bool, writer outputwriter.OutputWriter, similarIssues ...formats.SourceCodeRow) (content string) { switch commentType { case IacComment: diff --git a/utils/comment_test.go b/utils/comment_test.go index 79790c3f6..c0f6a0fe0 100644 --- a/utils/comment_test.go +++ b/utils/comment_test.go @@ -12,7 +12,6 @@ import ( ) func TestGetFrogbotReviewComments(t *testing.T) { - writer := &outputwriter.StandardOutput{} testCases := []struct { name string existingComments []vcsclient.CommentInfo @@ -44,7 +43,7 @@ func TestGetFrogbotReviewComments(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - output := getFrogbotComments(writer, tc.existingComments) + output := getFrogbotComments(tc.existingComments) assert.ElementsMatch(t, tc.expectedOutput, output) }) } @@ -205,8 +204,6 @@ func TestGroupSimilarJasIssues(t *testing.T) { func TestGetNewReviewComments(t *testing.T) { writer := &outputwriter.StandardOutput{} - - // repo := &Repository{OutputWriter: &outputwriter.StandardOutput{}} testCases := []struct { name string generateSecretsComments bool diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index 85296ebdc..faacc6de8 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -8,7 +8,6 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/severityutils" ) -// TODO: after refactor, move this to security-cli as a new formats or remove this and use the existing formats // Group issues by scan type type ScansIssuesCollection struct { formats.ScanStatus @@ -264,60 +263,6 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl evidences = append(evidences, evidence) } return - - // issueIdToApplicableInfo := map[string]formats.Applicability{} - // issueIdToIssue := map[string]formats.VulnerabilityOrViolationRow{} - // // Collect evidences from Violations - // for _, securityViolation := range ic.ScaViolations { - // issueId := results.GetIssueIdentifier(securityViolation.Cves, securityViolation.IssueId, "-") - // if _, exists := issueIdToIssue[issueId]; exists { - // // No need to add the same issue twice - // continue - // } - // for _, cve := range securityViolation.Cves { - // if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { - // // We only want applicable issues - // issueIdToIssue[issueId] = securityViolation - // issueIdToApplicableInfo[issueId] = *cve.Applicability - // } - // } - // } - // // Collect evidences from Vulnerabilities - // for _, vulnerability := range ic.ScaVulnerabilities { - // issueId := results.GetIssueIdentifier(vulnerability.Cves, vulnerability.IssueId, "-") - // if _, exists := issueIdToIssue[issueId]; exists { - // // No need to add the same issue twice - // continue - // } - // for _, cve := range vulnerability.Cves { - // if cve.Applicability != nil && cve.Applicability.Status == jasutils.Applicable.String() { - // // We only want applicable issues - // issueIdToIssue[issueId] = vulnerability - // issueIdToApplicableInfo[issueId] = *cve.Applicability - // } - // } - // } - // // Create ApplicableEvidences from collected data - // for issueId := range maps.Keys(issueIdToApplicableInfo) { - // issue := issueIdToIssue[issueId] - // applicableInfo := issueIdToApplicableInfo[issueId] - // remediation := "" - // if issue.JfrogResearchInformation != nil { - // remediation = issue.JfrogResearchInformation.Remediation - // } - // for _, evidence := range applicableInfo.Evidence { - // evidences = append(evidences, ApplicableEvidences{ - // Evidence: evidence, - // Severity: issue.Severity, - // ScannerDescription: applicableInfo.ScannerDescription, - // IssueId: results.GetIssueIdentifier(issue.Cves, issue.IssueId, ","), - // CveSummary: issue.Summary, - // ImpactedDependency: results.GetDependencyId(issue.ImpactedDependencyName, issue.ImpactedDependencyVersion), - // Remediation: remediation, - // }) - // } - // } - // return } // Violations @@ -343,68 +288,3 @@ func (ic *ScansIssuesCollection) GetTotalVulnerabilities(includeSecrets bool) in } return total } - -// func (ic *ScansIssuesCollection) GetTotal() - -// --------------------------------------- - -// func (ic *ScansIssuesCollection) GetScaIssues() (unique []formats.VulnerabilityOrViolationRow) { -// return append(ic.ScaVulnerabilities, ic.ScaViolations...) -// } - -// func (ic *ScansIssuesCollection) GetUniqueIacIssues() (unique []formats.SourceCodeRow) { -// return getUniqueJasIssues(ic.IacVulnerabilities, ic.IacViolations) -// } - -// func (ic *ScansIssuesCollection) GetUniqueSecretsIssues() (unique []formats.SourceCodeRow) { -// return getUniqueJasIssues(ic.SecretsVulnerabilities, ic.SecretsViolations) -// } - -// func (ic *ScansIssuesCollection) GetUniqueSastIssues() (unique []formats.SourceCodeRow) { -// return getUniqueJasIssues(ic.SastVulnerabilities, ic.SastViolations) -// } - -// func getUniqueJasIssues(vulnerabilities, violations []formats.SourceCodeRow) (unique []formats.SourceCodeRow) { -// parsedIssues := datastructures.MakeSet[string]() -// for _, violation := range violations { -// issueId := violation.Location.ToString() + "|" + violation.Finding -// if parsedIssues.Exists(issueId) { -// continue -// } -// parsedIssues.Add(issueId) -// unique = append(unique, violation) -// } -// for _, vulnerability := range vulnerabilities { -// issueId := vulnerability.Location.ToString() + "|" + vulnerability.Finding -// if parsedIssues.Exists(issueId) { -// continue -// } -// parsedIssues.Add(issueId) -// unique = append(unique, vulnerability) -// } -// return -// } - -// func (ic *ScansIssuesCollection) LicensesViolationsExists() bool { -// return len(ic.LicensesViolations) > 0 -// } - -// func (ic *ScansIssuesCollection) PresentableIssuesExists() bool { -// return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.LicensesViolationsExists() || ic.SastIssuesExists() -// } - -// func (ic *ScansIssuesCollection) ViolationsExists() bool { -// return len(ic.ScaViolations) > 0 || len(ic.IacViolations) > 0 || len(ic.SecretsViolations) > 0 || len(ic.SastViolations) > 0 || len(ic.LicensesViolations) > 0 -// } - -// func (ic *ScansIssuesCollection) CountIssuesCollectionFindings() int { -// count := 0 - -// count += len(ic.GetScaIssues()) -// count += len(ic.GetUniqueIacIssues()) -// count += len(ic.GetUniqueSecretsIssues()) -// count += len(ic.GetUniqueSastIssues()) -// count += len(ic.LicensesViolations) - -// return count -// } diff --git a/utils/outputwriter/markdowntable.go b/utils/outputwriter/markdowntable.go index b254f39bb..f4a14b27f 100644 --- a/utils/outputwriter/markdowntable.go +++ b/utils/outputwriter/markdowntable.go @@ -6,12 +6,6 @@ import ( ) const ( - - // tableRowFirstColumnSeparator = "| :---------------------: |" - // tableRowColumnSeparator = " :-----------------------------------: |" - // cellFirstCellPlaceholder = "| %s |" - // cellCellPlaceholder = " %s |" - cellDefaultValue = "-" firstCellPlaceholder = "| %s |" diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 40efb186c..68463552b 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -63,7 +63,7 @@ func GenerateReviewCommentContent(content string, writer OutputWriter) string { } // When can't create review comment, create a fallback comment by adding the location description to the content as a prefix -func GetFallbackReviewCommentContent(content string, location formats.Location, writer OutputWriter) string { +func GetFallbackReviewCommentContent(content string, location formats.Location) string { var contentBuilder strings.Builder contentBuilder.WriteString(MarkdownComment(ReviewCommentId)) WriteContent(&contentBuilder, getFallbackCommentLocationDescription(location), content) @@ -293,7 +293,7 @@ func PolicyViolationsContent(issues issues.ScansIssuesCollection, writer OutputW return ConvertContentToComments(policyViolationContent, writer, getDecoratorWithPolicyViolationTitle(writer)) } -func getDecoratorWithPolicyViolationTitle(writer OutputWriter) func(int, string) string { +func getDecoratorWithPolicyViolationTitle(writer OutputWriter) CommentDecorator { return func(commentCount int, content string) string { contentBuilder := strings.Builder{} // Decorate each part of the split content with a title as prefix and return the content @@ -314,7 +314,7 @@ func getSecurityViolationsContent(issues issues.ScansIssuesCollection, writer Ou return ConvertContentToComments(content, writer, getDecoratorWithSecurityViolationTitle(writer)) } -func getDecoratorWithSecurityViolationTitle(writer OutputWriter) func(int, string) string { +func getDecoratorWithSecurityViolationTitle(writer OutputWriter) CommentDecorator { return func(commentCount int, content string) string { contentBuilder := strings.Builder{} // Decorate each part of the split content with a title as prefix and return the content @@ -364,7 +364,7 @@ func getLicenseViolationsContent(issues issues.ScansIssuesCollection, writer Out return ConvertContentToComments(content, writer, getDecoratorWithLicenseViolationTitle(writer)) } -func getDecoratorWithLicenseViolationTitle(writer OutputWriter) func(int, string) string { +func getDecoratorWithLicenseViolationTitle(writer OutputWriter) CommentDecorator { return func(commentCount int, content string) string { contentBuilder := strings.Builder{} // Decorate each part of the split content with a title as prefix and return the content @@ -450,7 +450,7 @@ func GetVulnerabilitiesContent(vulnerabilities []formats.VulnerabilityOrViolatio return ConvertContentToComments(content, writer, getDecoratorWithScaVulnerabilitiesTitle(writer)) } -func getDecoratorWithScaVulnerabilitiesTitle(writer OutputWriter) func(int, string) string { +func getDecoratorWithScaVulnerabilitiesTitle(writer OutputWriter) CommentDecorator { return func(commentCount int, content string) string { contentBuilder := strings.Builder{} // Decorate each part of the split content with a title as prefix and return the content diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index f56954c21..d2a81479f 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -810,7 +810,7 @@ func TestGenerateReviewComment(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) output := GenerateReviewCommentContent(content, test.writer) if tc.location != nil { - output = GetFallbackReviewCommentContent(content, *tc.location, test.writer) + output = GetFallbackReviewCommentContent(content, *tc.location) } assert.Equal(t, expectedOutput, output) }) From 9be45eab80486a623c5367ed6b03c93f17337e28 Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 11 Dec 2024 11:32:08 +0200 Subject: [PATCH 21/34] update sec --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8bc18f2ff..05da3d82a 100644 --- a/go.mod +++ b/go.mod @@ -122,7 +122,7 @@ require ( // eranturgeman:jas-violations-support // replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211070414-848d30bb3042 +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev diff --git a/go.sum b/go.sum index e60b7a09c..e50ff1872 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211070414-848d30bb3042 h1:0jz+yrWt4B5oS1jGMU78qgywpOr+Is4cLkGCZjlzpQ8= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211070414-848d30bb3042/go.mod h1:c4GycFVqXqYOjI/1FdJ1lUf//kqQBGEzds1iSn73OaM= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc h1:F2tF/yCRi9oXGBawFhWcyod6F56Zb02VG/RtxXjUZDw= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc/go.mod h1:c4GycFVqXqYOjI/1FdJ1lUf//kqQBGEzds1iSn73OaM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= From b8957ec11ce05cebdb2d80fe09821c1110102ffe Mon Sep 17 00:00:00 2001 From: attiasas Date: Sun, 15 Dec 2024 11:39:14 +0200 Subject: [PATCH 22/34] update sec and client --- go.mod | 19 +++++++++-------- go.sum | 28 ++++++++++++------------- utils/analytics.go | 3 +-- utils/analytics_test.go | 3 +-- utils/params.go | 6 +++--- utils/scandetails.go | 45 +++++++++++++++++++++-------------------- 6 files changed, 53 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index 05da3d82a..ce5d07b4e 100644 --- a/go.mod +++ b/go.mod @@ -106,14 +106,14 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.26.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.27.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -121,14 +121,17 @@ require ( ) // eranturgeman:jas-violations-support -// replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241215092417-d288e4c9056e + +// replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev // attiasas:add_repo_context_scan_graph -replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a +replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241215085345-1cce31921430 + +// replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go dev diff --git a/go.sum b/go.sum index e50ff1872..6e31a0a4b 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a h1:47orWZJqdB4YIiqnYd0ysEjvqXiwy3eadwKkHo6s1qg= -github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a/go.mod h1:1a7bmQHkRmPEza9wva2+WVrYzrGbosrMymq57kyG5gU= +github.com/attiasas/jfrog-client-go v0.0.0-20241215085345-1cce31921430 h1:2SgkgR15RVfLNKpwcCG+kUC6+xCimMDGJ5MbkuA7Sk0= +github.com/attiasas/jfrog-client-go v0.0.0-20241215085345-1cce31921430/go.mod h1:3rdSCzKoN1l3wklL33D7nu+nB3089rIgxwyoprFdCaw= github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= @@ -59,8 +59,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc h1:F2tF/yCRi9oXGBawFhWcyod6F56Zb02VG/RtxXjUZDw= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc/go.mod h1:c4GycFVqXqYOjI/1FdJ1lUf//kqQBGEzds1iSn73OaM= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241215092417-d288e4c9056e h1:/P79HxXlNWUq5aJO52BRVGduFQQWZR2rwM+sZmdV4jY= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241215092417-d288e4c9056e/go.mod h1:y/SU5vJdL876jwVsxlOsOa1WmIPI/P80IlDusH9V9mA= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= @@ -299,8 +299,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -334,8 +334,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -365,16 +365,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -384,8 +384,8 @@ 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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/utils/analytics.go b/utils/analytics.go index aeaf31e4e..a36274b7a 100644 --- a/utils/analytics.go +++ b/utils/analytics.go @@ -6,11 +6,10 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-security/utils/xsc" - "github.com/jfrog/jfrog-client-go/xray/services" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" ) -func CreateScanEvent(serviceDetails *config.ServerDetails, gitInfo *services.XscGitInfoContext, scanType string) *xscservices.XscAnalyticsGeneralEvent { +func CreateScanEvent(serviceDetails *config.ServerDetails, gitInfo *xscservices.XscGitInfoContext, scanType string) *xscservices.XscAnalyticsGeneralEvent { event := xsc.CreateAnalyticsEvent(xscservices.FrogbotProduct, xscservices.FrogbotType, serviceDetails) event.ProductVersion = FrogbotVersion event.FrogbotScanType = scanType diff --git a/utils/analytics_test.go b/utils/analytics_test.go index 082834a4c..a722ad9dd 100644 --- a/utils/analytics_test.go +++ b/utils/analytics_test.go @@ -4,13 +4,12 @@ import ( "testing" "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-client-go/xray/services" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" "github.com/stretchr/testify/assert" ) func TestCreateAnalyticsGeneralEvent(t *testing.T) { - gitInfoContext := &services.XscGitInfoContext{ + gitInfoContext := &xscservices.XscGitInfoContext{ GitRepoHttpsCloneUrl: "http://localhost:8080/my-user/my-project.git", GitRepoName: "my-project", GitProject: "my-user", diff --git a/utils/params.go b/utils/params.go index af2a6d12a..099baaa40 100644 --- a/utils/params.go +++ b/utils/params.go @@ -307,9 +307,9 @@ func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { jp.ViolationContext = ProjectContext } } - // if jp.ViolationContext == None { - // jp.ViolationContext = GitRepoContext - // } + if jp.ViolationContext == None { + jp.ViolationContext = GitRepoContext + } return } diff --git a/utils/scandetails.go b/utils/scandetails.go index 78a6cb565..15d3671ed 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -19,12 +19,14 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" + xscservices "github.com/jfrog/jfrog-client-go/xsc/services" ) type ScanDetails struct { *Project *Git *services.XrayGraphScanParams + *xscservices.XscGitInfoContext *config.ServerDetails client vcsclient.VcsClient failOnInstallationErrors bool @@ -139,9 +141,10 @@ func (sc *ScanDetails) AllowPartialResults() bool { func (sc *ScanDetails) CreateCommonGraphScanParams() *scangraph.CommonGraphScanParams { commonParams := &scangraph.CommonGraphScanParams{ - RepoPath: sc.RepoPath, - Watches: sc.Watches, - ScanType: sc.ScanType, + RepoPath: sc.RepoPath, + Watches: sc.Watches, + GitRepoHttpsCloneUrl: sc.Git.RepositoryCloneUrl, + ScanType: sc.ScanType, } if sc.ProjectKey == "" { commonParams.ProjectKey = os.Getenv(coreutils.Project) @@ -154,7 +157,8 @@ func (sc *ScanDetails) CreateCommonGraphScanParams() *scangraph.CommonGraphScanP } func (sc *ScanDetails) HasViolationContext() bool { - return sc.ProjectKey != "" || len(sc.Watches) > 0 || sc.RepoPath != "" || (sc.XscGitInfoContext != nil && sc.XscGitInfoContext.GitRepoHttpsCloneUrl != "") + // TODO: infer this info from the results of the scan and delete this method. + return sc.ProjectKey != "" || sc.Git.RepositoryCloneUrl != "" || len(sc.Watches) > 0 || sc.RepoPath != "" } func createXrayScanParams(httpCloneUrl string, watches []string, project string, includeLicenses bool) (params *services.XrayGraphScanParams) { @@ -162,19 +166,13 @@ func createXrayScanParams(httpCloneUrl string, watches []string, project string, ScanType: services.Dependency, IncludeLicenses: includeLicenses, } - if len(httpCloneUrl) > 0 && params.XscGitInfoContext == nil { - // TODO: control with other var, this is always true. - if project != "" { - log.Warn("Using git URL as violation context, project key will be ignored.") - } - params.ProjectKey = "" - - params.Watches = watches - - params.XscGitInfoContext = &services.XscGitInfoContext{ - GitRepoHttpsCloneUrl: httpCloneUrl, - } - return + defer func() { + log.Debug(fmt.Sprintf("Passing XrayGraphScanParams: %+v", params)) + }() + if len(httpCloneUrl) > 0 { + // We always pass the clone URL to the platform, with other parameters combined. + // the logic to decide whether to use it or not is in the audit command. + params.GitRepoHttpsCloneUrl = httpCloneUrl } if len(watches) > 0 { params.Watches = watches @@ -184,7 +182,11 @@ func createXrayScanParams(httpCloneUrl string, watches []string, project string, params.ProjectKey = project return } - params.IncludeVulnerabilities = true + // TODO: --vuln flag, should be able to get both vulnerabilities and violations. + // since if watch is defined on git-repo resource there is no way to get vulnerabilities. + if len(httpCloneUrl) == 0 { + params.IncludeVulnerabilities = true + } return } @@ -213,7 +215,6 @@ func (sc *ScanDetails) RunInstallAndAudit(workDirs ...string) (auditResults *res SetGraphBasicParams(auditBasicParams). SetCommonGraphScanParams(sc.CreateCommonGraphScanParams()). SetConfigProfile(sc.configProfile). - SetGitInfoContext(sc.XscGitInfoContext). SetMultiScanId(sc.MultiScanId). SetStartTime(sc.StartTime) @@ -234,7 +235,7 @@ func (sc *ScanDetails) SetXscGitInfoContext(scannedBranch, gitProject string, cl // ScannedBranch - name of the branch we are scanning. // GitProject - [Optional] relevant for azure repos and Bitbucket server. // Client vscClient -func (sc *ScanDetails) createGitInfoContext(scannedBranch, gitProject string, client vcsclient.VcsClient) (gitInfo *services.XscGitInfoContext, err error) { +func (sc *ScanDetails) createGitInfoContext(scannedBranch, gitProject string, client vcsclient.VcsClient) (gitInfo *xscservices.XscGitInfoContext, err error) { latestCommit, err := client.GetLatestCommit(context.Background(), sc.RepoOwner, sc.RepoName, scannedBranch) if err != nil { return nil, fmt.Errorf("failed getting latest commit, repository: %s, branch: %s. error: %s ", sc.RepoName, scannedBranch, err.Error()) @@ -243,11 +244,11 @@ func (sc *ScanDetails) createGitInfoContext(scannedBranch, gitProject string, cl if gitProject == "" { gitProject = sc.RepoOwner } - gitInfo = &services.XscGitInfoContext{ + gitInfo = &xscservices.XscGitInfoContext{ // Use Clone URLs as Repo Url, on browsers it will redirect to repository URLS. GitRepoHttpsCloneUrl: sc.Git.RepositoryCloneUrl, GitRepoName: sc.RepoName, - GitProvider: sc.GitProvider.String(), + GitProvider: sc.Git.GitProvider.String(), GitProject: gitProject, BranchName: scannedBranch, LastCommitUrl: latestCommit.Url, From bf824b8950cdfda038cec986b80c771cb30dc5e5 Mon Sep 17 00:00:00 2001 From: attiasas Date: Mon, 16 Dec 2024 15:31:36 +0200 Subject: [PATCH 23/34] update versions --- go.mod | 6 +----- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9232fc8ce..8eb66aadf 100644 --- a/go.mod +++ b/go.mod @@ -27,10 +27,7 @@ require ( github.com/CycloneDX/cyclonedx-go v0.9.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v1.1.2 // indirect - github.com/VividCortex/ewma v1.2.0 // indirect - github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/beevik/etree v1.4.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/c-bata/go-prompt v0.2.5 // indirect github.com/chzyer/readline v1.5.1 // indirect @@ -96,7 +93,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/ulikunitz/xz v0.5.12 // indirect github.com/urfave/cli v1.22.16 // indirect - github.com/vbauerster/mpb/v8 v8.8.3 // indirect github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect github.com/xanzy/go-gitlab v0.110.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect @@ -122,7 +118,7 @@ require ( ) // eranturgeman:jas-violations-support -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241215092417-d288e4c9056e +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241216131728-40f074cfa72b // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc diff --git a/go.sum b/go.sum index 8dffda79b..96c8ab6eb 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/attiasas/jfrog-client-go v0.0.0-20241215085345-1cce31921430 h1:2SgkgR15RVfLNKpwcCG+kUC6+xCimMDGJ5MbkuA7Sk0= github.com/attiasas/jfrog-client-go v0.0.0-20241215085345-1cce31921430/go.mod h1:3rdSCzKoN1l3wklL33D7nu+nB3089rIgxwyoprFdCaw= -github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= -github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -59,8 +57,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241215092417-d288e4c9056e h1:/P79HxXlNWUq5aJO52BRVGduFQQWZR2rwM+sZmdV4jY= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241215092417-d288e4c9056e/go.mod h1:y/SU5vJdL876jwVsxlOsOa1WmIPI/P80IlDusH9V9mA= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241216131728-40f074cfa72b h1:SROhNEOLiRim0DGcMN2P9hXBLirD7iyINj5M7C5rLsA= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241216131728-40f074cfa72b/go.mod h1:xTaYKgsO6t5SvNyV4W5V2MfGcn26jF/UVWDxnT6fcvc= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= From f6421f9b021b9092235644fceb559cad5e53c4fb Mon Sep 17 00:00:00 2001 From: attiasas Date: Fri, 20 Dec 2024 18:14:59 +0300 Subject: [PATCH 24/34] remove error state, user result context --- go.mod | 4 +- go.sum | 8 +- scanpullrequest/scanpullrequest.go | 34 ++-- scanpullrequest/scanpullrequest_test.go | 2 +- scanrepository/scanrepository.go | 17 +- scanrepository/scanrepository_test.go | 148 +++++++++--------- .../structure/error_simplified.md | 39 ----- .../structure/error_standard.md | 26 --- .../summary/summary_both_simplified.md | 15 ++ .../summary/summary_both_standard.md | 11 ++ .../summary/summary_error_simplified.md | 2 +- .../summary/summary_error_standard.md | 2 +- .../summary/summary_simplified.md | 2 +- .../summary/summary_standard.md | 2 +- .../summary/summary_violation_simplified.md | 2 +- .../summary/summary_violation_standard.md | 2 +- utils/comment.go | 49 +++--- utils/consts.go | 4 +- utils/issues/issuescollection.go | 45 ++++-- utils/issues/issuescollection_test.go | 39 ++++- utils/outputwriter/markdowntable.go | 12 +- utils/outputwriter/markdowntable_test.go | 8 +- utils/outputwriter/outputcontent.go | 111 +++++++------ utils/outputwriter/outputcontent_test.go | 65 +++----- utils/params.go | 18 +-- utils/scandetails.go | 79 +++------- utils/scandetails_test.go | 100 +++++++----- utils/utils.go | 26 +-- 28 files changed, 421 insertions(+), 451 deletions(-) delete mode 100644 testdata/messages/summarycomment/structure/error_simplified.md delete mode 100644 testdata/messages/summarycomment/structure/error_standard.md create mode 100644 testdata/messages/summarycomment/summary/summary_both_simplified.md create mode 100644 testdata/messages/summarycomment/summary/summary_both_standard.md diff --git a/go.mod b/go.mod index 8eb66aadf..a552ba650 100644 --- a/go.mod +++ b/go.mod @@ -118,7 +118,7 @@ require ( ) // eranturgeman:jas-violations-support -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241216131728-40f074cfa72b +replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241219083824-04219907a60e // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc @@ -127,7 +127,7 @@ replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev // attiasas:add_repo_context_scan_graph -replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241215085345-1cce31921430 +replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241218124804-45bdc2205dc7 // replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a diff --git a/go.sum b/go.sum index 96c8ab6eb..0d75398bd 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/attiasas/jfrog-client-go v0.0.0-20241215085345-1cce31921430 h1:2SgkgR15RVfLNKpwcCG+kUC6+xCimMDGJ5MbkuA7Sk0= -github.com/attiasas/jfrog-client-go v0.0.0-20241215085345-1cce31921430/go.mod h1:3rdSCzKoN1l3wklL33D7nu+nB3089rIgxwyoprFdCaw= +github.com/attiasas/jfrog-client-go v0.0.0-20241218124804-45bdc2205dc7 h1:SR2SMTTwKikNZmL+8Tn/r34+84IuyLPDsTyRVDlJs3Y= +github.com/attiasas/jfrog-client-go v0.0.0-20241218124804-45bdc2205dc7/go.mod h1:3rdSCzKoN1l3wklL33D7nu+nB3089rIgxwyoprFdCaw= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -57,8 +57,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241216131728-40f074cfa72b h1:SROhNEOLiRim0DGcMN2P9hXBLirD7iyINj5M7C5rLsA= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241216131728-40f074cfa72b/go.mod h1:xTaYKgsO6t5SvNyV4W5V2MfGcn26jF/UVWDxnT6fcvc= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241219083824-04219907a60e h1:yepWeXijidYX+g/xTDier6wwvPM97GE/bi61EewqlnY= +github.com/eranturgeman/jfrog-cli-security v0.0.0-20241219083824-04219907a60e/go.mod h1:UwS/jqUIBaEdmNyDhliUnkl7YUWiM71Zp9D6Yrhtz6A= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 7fca1ad24..cf1866a2d 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -95,7 +95,7 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er // Audit PR code issues, err := auditPullRequest(repo, client) if err != nil { - return errors.Join(err, utils.HandlePullRequestErrorComment(issues, repo, client, int(pullRequestDetails.ID), err)) + return } // Output results @@ -127,21 +127,14 @@ func toFailTaskStatus(repo *utils.Repository, issues *issues.ScansIssuesCollecti // Downloads Pull Requests branches code and audits them func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) (issuesCollection *issues.ScansIssuesCollection, err error) { - repositoryInfo, err := client.GetRepositoryInfo(context.Background(), repoConfig.RepoOwner, repoConfig.RepoName) if err != nil { return } repoConfig.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP - gitRepoContextValue := "" - if repoConfig.ViolationContext == utils.GitRepoContext { - // The violation context is the Git repository, inject the Git repository context to the scan details - gitRepoContextValue = repositoryInfo.CloneInfo.HTTP - } - scanDetails := utils.NewScanDetails(client, &repoConfig.Server, &repoConfig.Git). - SetXrayGraphScanParams(gitRepoContextValue, repoConfig.Watches, repoConfig.JFrogProjectKey, len(repoConfig.AllowedLicenses) > 0). + SetResultsContext(repositoryInfo.CloneInfo.HTTP, repoConfig.Watches, repoConfig.JFrogProjectKey, repoConfig.IncludeVulnerabilities, len(repoConfig.AllowedLicenses) > 0). SetFixableOnly(repoConfig.FixableOnly). SetFailOnInstallationErrors(*repoConfig.FailOnSecurityIssues). SetConfigProfile(repoConfig.ConfigProfile). @@ -151,7 +144,6 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) if scanDetails, err = scanDetails.SetMinSeverity(repoConfig.MinSeverity); err != nil { return } - scanDetails.XrayVersion = repoConfig.XrayVersion scanDetails.XscVersion = repoConfig.XscVersion @@ -200,7 +192,7 @@ func auditPullRequestInProject(repoConfig *utils.Repository, scanDetails *utils. sourceResults = scanDetails.RunInstallAndAudit(workingDirs...) if err = sourceResults.GetErrors(); err != nil { // We get the scan status even if the scan failed to report the scan status in the summary - auditIssues = getResultScanStatues(scanDetails.IncludeVulnerabilities, scanDetails.HasViolationContext(), sourceResults) + auditIssues = getResultScanStatues(sourceResults) return } @@ -208,7 +200,7 @@ func auditPullRequestInProject(repoConfig *utils.Repository, scanDetails *utils. repoConfig.OutputWriter.SetJasOutputFlags(sourceResults.EntitledForJas, sourceResults.HasJasScansResults(jasutils.Applicability)) // Get all issues that exist in the source branch if repoConfig.IncludeAllVulnerabilities { - if auditIssues, err = getAllIssues(sourceResults, repoConfig.AllowedLicenses, scanDetails.IncludeVulnerabilities, scanDetails.HasViolationContext()); err != nil { + if auditIssues, err = getAllIssues(sourceResults, repoConfig.AllowedLicenses); err != nil { return } utils.ConvertSarifPathsToRelative(auditIssues, sourceBranchWd) @@ -242,12 +234,12 @@ func auditTargetBranch(repoConfig *utils.Repository, scanDetails *utils.ScanDeta targetResults = scanDetails.RunInstallAndAudit(workingDirs...) if err = targetResults.GetErrors(); err != nil { // We get the scan status even if the scan failed to report the scan status in the summary - newIssues = getResultScanStatues(scanDetails.IncludeVulnerabilities, scanDetails.HasViolationContext(), sourceScanResults, targetResults) + newIssues = getResultScanStatues(sourceScanResults, targetResults) return } // Get newly added issues - newIssues, err = getNewlyAddedIssues(targetResults, sourceScanResults, repoConfig.AllowedLicenses, scanDetails.IncludeVulnerabilities, scanDetails.HasViolationContext()) + newIssues, err = getNewlyAddedIssues(targetResults, sourceScanResults, repoConfig.AllowedLicenses, targetResults.IncludesVulnerabilities(), targetResults.HasViolationContext()) return } @@ -301,11 +293,11 @@ func tryCheckoutToMostCommonAncestor(scanDetails *utils.ScanDetails, baseBranch, return gitManager.CheckoutToHash(bestAncestorHash) } -func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses []string, includeVulnerabilities, hasViolationContext bool) (*issues.ScansIssuesCollection, error) { +func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses []string) (*issues.ScansIssuesCollection, error) { log.Info("Frogbot is configured to show all issues") simpleJsonResults, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{ - IncludeVulnerabilities: includeVulnerabilities, - HasViolationContext: hasViolationContext, + IncludeVulnerabilities: cmdResults.IncludesVulnerabilities(), + HasViolationContext: cmdResults.HasViolationContext(), AllowedLicenses: allowedLicenses, IncludeLicenses: true, SimplifiedOutput: true, @@ -314,6 +306,7 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] return nil, err } return &issues.ScansIssuesCollection{ + ResultContext: cmdResults.ResultContext, ScanStatus: simpleJsonResults.Statuses, ScaVulnerabilities: simpleJsonResults.Vulnerabilities, ScaViolations: simpleJsonResults.SecurityViolations, @@ -330,10 +323,10 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] }, nil } -func getResultScanStatues(includeVulnerabilities, hasViolationContext bool, cmdResults ...*results.SecurityCommandResults) *issues.ScansIssuesCollection { - convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext, SimplifiedOutput: true}) +func getResultScanStatues(cmdResults ...*results.SecurityCommandResults) *issues.ScansIssuesCollection { converted := make([]formats.SimpleJsonResults, len(cmdResults)) for i, cmdResult := range cmdResults { + convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: cmdResult.IncludesVulnerabilities(), HasViolationContext: cmdResult.HasViolationContext(), SimplifiedOutput: true}) var err error if converted[i], err = convertor.ConvertToSimpleJson(cmdResult); err != nil { log.Debug(fmt.Sprintf("Failed to get scan status for failed scan #%d. Error: %s", i, err.Error())) @@ -354,7 +347,8 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe if err != nil { return } - newIssues = &issues.ScansIssuesCollection{} + // ResultContext is general attribute similar for all results, taking it from the source results + newIssues = &issues.ScansIssuesCollection{ResultContext: sourceResults.ResultContext} newIssues.ScanStatus = getScanStatus(simpleJsonTarget, simpleJsonSource) // Get the unique sca vulnerabilities and violations between the source and target branches newIssues.ScaVulnerabilities = getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities) diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index b245ca9d7..7a54c3355 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -588,7 +588,7 @@ func TestGetAllIssues(t *testing.T) { }, } - issuesRows, err := getAllIssues(auditResults, allowedLicenses, true, false) + issuesRows, err := getAllIssues(auditResults, allowedLicenses) if assert.NoError(t, err) { assert.ElementsMatch(t, expectedOutput.ScaVulnerabilities, issuesRows.ScaVulnerabilities) diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index d387e7022..1bd4a0d9a 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -134,12 +134,7 @@ func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Reposito if err != nil { return } - gitRepoContextValue := "" - if repository.ViolationContext == utils.GitRepoContext { - // The violation context is the Git repository, inject the Git repository context to the scan details - gitRepoContextValue = repositoryInfo.CloneInfo.HTTP - } - cfp.scanDetails.SetXrayGraphScanParams(gitRepoContextValue, repository.Watches, repository.JFrogProjectKey, len(repository.AllowedLicenses) > 0) + cfp.scanDetails.SetResultsContext(repositoryInfo.CloneInfo.HTTP, repository.Watches, repository.JFrogProjectKey, repository.IncludeVulnerabilities, len(repository.AllowedLicenses) > 0) cfp.scanDetails.XrayVersion = cfp.XrayVersion cfp.scanDetails.XscVersion = cfp.XscVersion @@ -181,7 +176,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) (i } continue } - if summary, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: cfp.scanDetails.IncludeVulnerabilities, HasViolationContext: cfp.scanDetails.HasViolationContext()}).ConvertToSummary(scanResults); err != nil { + if summary, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: scanResults.IncludesVulnerabilities(), HasViolationContext: scanResults.HasViolationContext()}).ConvertToSummary(scanResults); err != nil { return totalFindings, err } else { findingCount := summary.GetTotalViolations() @@ -195,7 +190,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) (i // Uploads Sarif results to GitHub in order to view the scan in the code scanning UI // Currently available on GitHub only and JFrog Advance Security package // Only if Jas entitlement is available - if err = utils.UploadSarifResultsToGithubSecurityTab(scanResults, repository, cfp.scanDetails.BaseBranch(), cfp.scanDetails.Client(), cfp.scanDetails.IncludeVulnerabilities, cfp.scanDetails.HasViolationContext()); err != nil { + if err = utils.UploadSarifResultsToGithubSecurityTab(scanResults, repository, cfp.scanDetails.BaseBranch(), cfp.scanDetails.Client()); err != nil { log.Warn(err) } } @@ -237,7 +232,7 @@ func (cfp *ScanRepositoryCmd) scan(currentWorkingDir string) (*results.SecurityC } func (cfp *ScanRepositoryCmd) getVulnerabilitiesMap(scanResults *results.SecurityCommandResults) (map[string]*utils.VulnerabilityDetails, error) { - vulnerabilitiesMap, err := cfp.createVulnerabilitiesMap(scanResults, cfp.scanDetails.IncludeVulnerabilities, cfp.scanDetails.HasViolationContext()) + vulnerabilitiesMap, err := cfp.createVulnerabilitiesMap(scanResults) if err != nil { return nil, err } @@ -569,9 +564,9 @@ func (cfp *ScanRepositoryCmd) cloneRepositoryOrUseLocalAndCheckoutToBranch() (te } // Create a vulnerabilities map - a map with 'impacted package' as a key and all the necessary information of this vulnerability as value. -func (cfp *ScanRepositoryCmd) createVulnerabilitiesMap(scanResults *results.SecurityCommandResults, includeVulnerabilities, hasViolationContext bool) (map[string]*utils.VulnerabilityDetails, error) { +func (cfp *ScanRepositoryCmd) createVulnerabilitiesMap(scanResults *results.SecurityCommandResults) (map[string]*utils.VulnerabilityDetails, error) { vulnerabilitiesMap := map[string]*utils.VulnerabilityDetails{} - simpleJsonResult, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: includeVulnerabilities, HasViolationContext: hasViolationContext}).ConvertToSimpleJson(scanResults) + simpleJsonResult, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: scanResults.IncludesVulnerabilities(), HasViolationContext: scanResults.HasViolationContext()}).ConvertToSimpleJson(scanResults) if err != nil { return nil, err } diff --git a/scanrepository/scanrepository_test.go b/scanrepository/scanrepository_test.go index 162f6837a..3748955b2 100644 --- a/scanrepository/scanrepository_test.go +++ b/scanrepository/scanrepository_test.go @@ -435,10 +435,8 @@ func TestPackageTypeFromScan(t *testing.T) { frogbotParams.Projects[0].InstallCommandName = pkg.commandName frogbotParams.Projects[0].InstallCommandArgs = pkg.commandArgs scanSetup := utils.ScanDetails{ - XrayGraphScanParams: &services.XrayGraphScanParams{ - XrayVersion: xrayVersion, - XscVersion: xscVersion, - }, + XrayVersion: xrayVersion, + XscVersion: xscVersion, Project: &frogbotParams.Projects[0], ServerDetails: &frogbotParams.Server, } @@ -487,44 +485,47 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { }, { name: "Scan results with vulnerabilities and no violations", - scanResults: &results.SecurityCommandResults{Targets: []*results.TargetResults{{ - ScanTarget: results.ScanTarget{Target: "target1"}, - ScaResults: &results.ScaScanResults{ - XrayResults: validations.NewMockScaResults( - services.ScanResponse{ - Vulnerabilities: []services.Vulnerability{ - { - Cves: []services.Cve{ - {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, - {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, - }, - Severity: "Critical", - Components: map[string]services.Component{ - "vuln1": { - FixedVersions: []string{"1.9.1", "2.0.3", "2.0.5"}, - ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "vuln1"}}}, + scanResults: &results.SecurityCommandResults{ + ResultContext: results.ResultContext{IncludeVulnerabilities: true}, + Targets: []*results.TargetResults{{ + ScanTarget: results.ScanTarget{Target: "target1"}, + ScaResults: &results.ScaScanResults{ + XrayResults: validations.NewMockScaResults( + services.ScanResponse{ + Vulnerabilities: []services.Vulnerability{ + { + Cves: []services.Cve{ + {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, + {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, + }, + Severity: "Critical", + Components: map[string]services.Component{ + "vuln1": { + FixedVersions: []string{"1.9.1", "2.0.3", "2.0.5"}, + ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "vuln1"}}}, + }, }, }, - }, - { - Cves: []services.Cve{ - {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, - {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, - }, - Severity: "High", - Components: map[string]services.Component{ - "vuln2": { - FixedVersions: []string{"2.4.1", "2.6.3", "2.8.5"}, - ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "vuln1"}, {ComponentId: "vuln2"}}}, + { + Cves: []services.Cve{ + {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, + {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, + }, + Severity: "High", + Components: map[string]services.Component{ + "vuln2": { + FixedVersions: []string{"2.4.1", "2.6.3", "2.8.5"}, + ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "vuln1"}, {ComponentId: "vuln2"}}}, + }, }, }, }, }, - }, - ), - }, - JasResults: &results.JasScansResults{}, - }}}, + ), + }, + JasResults: &results.JasScansResults{}, + }}, + }, expectedMap: map[string]*utils.VulnerabilityDetails{ "vuln1": { SuggestedFixedVersion: "1.9.1", @@ -539,46 +540,51 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { }, { name: "Scan results with violations and no vulnerabilities", - scanResults: &results.SecurityCommandResults{Targets: []*results.TargetResults{{ - ScanTarget: results.ScanTarget{Target: "target1"}, - ScaResults: &results.ScaScanResults{ - XrayResults: validations.NewMockScaResults( - services.ScanResponse{ - Violations: []services.Violation{ - { - ViolationType: "security", - Cves: []services.Cve{ - {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, - {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, - }, - Severity: "Critical", - Components: map[string]services.Component{ - "viol1": { - FixedVersions: []string{"1.9.1", "2.0.3", "2.0.5"}, - ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "viol1"}}}, + scanResults: &results.SecurityCommandResults{ + ResultContext: results.ResultContext{IncludeVulnerabilities: true, Watches: []string{"w1"}}, + Targets: []*results.TargetResults{{ + ScanTarget: results.ScanTarget{Target: "target1"}, + ScaResults: &results.ScaScanResults{ + XrayResults: validations.NewMockScaResults( + services.ScanResponse{ + Violations: []services.Violation{ + { + ViolationType: "security", + WatchName: "w1", + Cves: []services.Cve{ + {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, + {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, + }, + Severity: "Critical", + Components: map[string]services.Component{ + "viol1": { + FixedVersions: []string{"1.9.1", "2.0.3", "2.0.5"}, + ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "viol1"}}}, + }, }, }, - }, - { - ViolationType: "security", - Cves: []services.Cve{ - {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, - {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, - }, - Severity: "High", - Components: map[string]services.Component{ - "viol2": { - FixedVersions: []string{"2.4.1", "2.6.3", "2.8.5"}, - ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "viol1"}, {ComponentId: "viol2"}}}, + { + ViolationType: "security", + WatchName: "w1", + Cves: []services.Cve{ + {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, + {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, + }, + Severity: "High", + Components: map[string]services.Component{ + "viol2": { + FixedVersions: []string{"2.4.1", "2.6.3", "2.8.5"}, + ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "viol1"}, {ComponentId: "viol2"}}}, + }, }, }, }, }, - }, - ), - }, - JasResults: &results.JasScansResults{}, - }}}, + ), + }, + JasResults: &results.JasScansResults{}, + }}, + }, expectedMap: map[string]*utils.VulnerabilityDetails{ "viol1": { SuggestedFixedVersion: "1.9.1", @@ -595,7 +601,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - fixVersionsMap, err := cfp.createVulnerabilitiesMap(testCase.scanResults, true, true) + fixVersionsMap, err := cfp.createVulnerabilitiesMap(testCase.scanResults) assert.NoError(t, err) for name, expectedVuln := range testCase.expectedMap { actualVuln, exists := fixVersionsMap[name] diff --git a/testdata/messages/summarycomment/structure/error_simplified.md b/testdata/messages/summarycomment/structure/error_simplified.md deleted file mode 100644 index 00fc7499d..000000000 --- a/testdata/messages/summarycomment/structure/error_simplified.md +++ /dev/null @@ -1,39 +0,0 @@ - - -[comment]: <> (FrogbotReviewComment) - - - - ---- -## 📗 Scan Summary - ---- -- Frogbot attempted to scan for but encountered an error. - ---- -### ⚠️ Error Details - ---- - - - ---- -#### Error: - ---- -Some error that occurred in scan - - ---- -#### Next Steps: - ---- -1. Please try to rerun the scan. -2. If the issue persists, consider checking the [Frogbot documentation](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) for troubleshooting tips. -3. If you still need assistance, feel free to reach out to [JFrog Support](https://jfrog.com/support/). - -Thank you for your understanding! - ---- -[🐸 JFrog Frogbot](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) \ No newline at end of file diff --git a/testdata/messages/summarycomment/structure/error_standard.md b/testdata/messages/summarycomment/structure/error_standard.md deleted file mode 100644 index 67fff3b91..000000000 --- a/testdata/messages/summarycomment/structure/error_standard.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[comment]: <> (FrogbotReviewComment) - - - -## 📗 Scan Summary -- Frogbot attempted to scan for but encountered an error. -
⚠️ Error Details - -#### Error: -Some error that occurred in scan - -#### Next Steps: -1. Please try to rerun the scan. -2. If the issue persists, consider checking the [Frogbot documentation](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) for troubleshooting tips. -3. If you still need assistance, feel free to reach out to [JFrog Support](https://jfrog.com/support/). - -Thank you for your understanding!
- ---- -
- -[🐸 JFrog Frogbot](https://docs.jfrog-applications.jfrog.io/jfrog-applications/frogbot) - -
diff --git a/testdata/messages/summarycomment/summary/summary_both_simplified.md b/testdata/messages/summarycomment/summary/summary_both_simplified.md new file mode 100644 index 000000000..0f8be7f67 --- /dev/null +++ b/testdata/messages/summarycomment/summary/summary_both_simplified.md @@ -0,0 +1,15 @@ + + +--- +## 📗 Scan Summary + +--- +- Frogbot scanned for violations and vulnerabilities and found 9 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done | 6 Issues Found: ❗️ 1 Critical, 🔴 2 High, 🟠 1 Medium, 🟡 1 Low, ⚪️ 1 Unknown | +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done | 3 Issues Found: 🔴 2 High, 🟡 1 Low | +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_both_standard.md b/testdata/messages/summarycomment/summary/summary_both_standard.md new file mode 100644 index 000000000..48c8ae3b6 --- /dev/null +++ b/testdata/messages/summarycomment/summary/summary_both_standard.md @@ -0,0 +1,11 @@ + +## 📗 Scan Summary +- Frogbot scanned for violations and vulnerabilities and found 9 issues + +| Scan Category | Status | Security Issues | +| --------------------- | :-----------------------------------: | ----------------------------------- | +| **Software Composition Analysis** | ✅ Done |
6 Issues Found 1 Critical
2 High
1 Medium
1 Low
1 Unknown
| +| **Contextual Analysis** | ✅ Done | - | +| **Static Application Security Testing (SAST)** | ✅ Done |
3 Issues Found 2 High
1 Low
| +| **Secrets** | ✅ Done | - | +| **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_error_simplified.md b/testdata/messages/summarycomment/summary/summary_error_simplified.md index a090e66b8..ebe16f635 100644 --- a/testdata/messages/summarycomment/summary/summary_error_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_error_simplified.md @@ -4,4 +4,4 @@ ## 📗 Scan Summary --- -- Frogbot attempted to scan for but encountered an error. \ No newline at end of file +- Frogbot attempted to scan for violations but encountered an error. \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_error_standard.md b/testdata/messages/summarycomment/summary/summary_error_standard.md index 9544038d0..7849db553 100644 --- a/testdata/messages/summarycomment/summary/summary_error_standard.md +++ b/testdata/messages/summarycomment/summary/summary_error_standard.md @@ -1,3 +1,3 @@ ## 📗 Scan Summary -- Frogbot attempted to scan for but encountered an error. \ No newline at end of file +- Frogbot attempted to scan for violations but encountered an error. \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_simplified.md b/testdata/messages/summarycomment/summary/summary_simplified.md index e989c77da..64bab849c 100644 --- a/testdata/messages/summarycomment/summary/summary_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_simplified.md @@ -4,7 +4,7 @@ ## 📗 Scan Summary --- -- Frogbot scanned for and found 9 issues +- Frogbot scanned for vulnerabilities and found 9 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | diff --git a/testdata/messages/summarycomment/summary/summary_standard.md b/testdata/messages/summarycomment/summary/summary_standard.md index e0d63cbcf..5244ba6bf 100644 --- a/testdata/messages/summarycomment/summary/summary_standard.md +++ b/testdata/messages/summarycomment/summary/summary_standard.md @@ -1,6 +1,6 @@ ## 📗 Scan Summary -- Frogbot scanned for and found 9 issues +- Frogbot scanned for vulnerabilities and found 9 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | diff --git a/testdata/messages/summarycomment/summary/summary_violation_simplified.md b/testdata/messages/summarycomment/summary/summary_violation_simplified.md index e989c77da..52c74bae9 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_violation_simplified.md @@ -4,7 +4,7 @@ ## 📗 Scan Summary --- -- Frogbot scanned for and found 9 issues +- Frogbot scanned for violations and found 9 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | diff --git a/testdata/messages/summarycomment/summary/summary_violation_standard.md b/testdata/messages/summarycomment/summary/summary_violation_standard.md index e0d63cbcf..6f005e487 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_standard.md +++ b/testdata/messages/summarycomment/summary/summary_violation_standard.md @@ -1,6 +1,6 @@ ## 📗 Scan Summary -- Frogbot scanned for and found 9 issues +- Frogbot scanned for violations and found 9 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | diff --git a/utils/comment.go b/utils/comment.go index 9fe183697..b059a5a3c 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -32,20 +32,20 @@ const ( commentRemovalErrorMsg = "An error occurred while attempting to remove older Frogbot pull request comments:" ) -// In Scan PR, if there is an error, a comment will be added to the PR with the error message. -func HandlePullRequestErrorComment(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int, scanError error) (err error) { - if issues == nil { - log.Debug("Can't generate error comment without issues collection") - return - } - writer := repo.OutputWriter - for _, comment := range outputwriter.GetFrogbotErrorCommentContent([]string{outputwriter.ScanSummaryContent(*issues, getViolationContextText(repo.ViolationContext), repo.PullRequestSecretComments, writer)}, scanError, writer) { - if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, comment, pullRequestID); err != nil { - return errors.New("couldn't add pull request comment: " + err.Error()) - } - } - return -} +// // In Scan PR, if there is an error, a comment will be added to the PR with the error message. +// func HandlePullRequestErrorComment(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int, scanError error) (err error) { +// if issues == nil { +// log.Debug("Can't generate error comment without issues collection") +// return +// } +// writer := repo.OutputWriter +// for _, comment := range outputwriter.GetFrogbotErrorCommentContent([]string{outputwriter.ScanSummaryContent(*issues, getResultsContextText(repo.ViolationContext), repo.PullRequestSecretComments, writer)}, scanError, writer) { +// if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, comment, pullRequestID); err != nil { +// return errors.New("couldn't add pull request comment: " + err.Error()) +// } +// } +// return +// } // In Scan PR, if there are no issues, comments will be added to the PR with a message that there are no issues. func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int) (err error) { @@ -62,7 +62,7 @@ func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, re } // Add summary (SCA, license) scan comment - for _, comment := range generatePullRequestSummaryComment(issues, repo.ViolationContext, repo.PullRequestSecretComments, repo.OutputWriter) { + for _, comment := range generatePullRequestSummaryComment(*issues, repo.PullRequestSecretComments, repo.OutputWriter) { if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, comment, pullRequestID); err != nil { err = errors.New("couldn't add pull request comment: " + err.Error()) return @@ -123,28 +123,15 @@ func GenerateFixPullRequestDetails(vulnerabilities []formats.VulnerabilityOrViol return } -func getViolationContextText(violationContext ViolationContext) string { - switch violationContext { - case WatchContext: - return outputwriter.WatchViolations - case ProjectContext: - return outputwriter.ProjectViolations - case GitRepoContext: - return outputwriter.GitRepoViolations - default: - return outputwriter.NoViolations - } -} - -func generatePullRequestSummaryComment(issuesCollection *issues.ScansIssuesCollection, violationContext ViolationContext, includeSecrets bool, writer outputwriter.OutputWriter) []string { +func generatePullRequestSummaryComment(issuesCollection issues.ScansIssuesCollection, includeSecrets bool, writer outputwriter.OutputWriter) []string { if !issuesCollection.IssuesExists(includeSecrets) { // No Issues return outputwriter.GetMainCommentContent([]string{}, false, true, writer) } // Summary - content := []string{outputwriter.ScanSummaryContent(*issuesCollection, getViolationContextText(violationContext), includeSecrets, writer)} + content := []string{outputwriter.ScanSummaryContent(issuesCollection, issuesCollection.ResultContext, includeSecrets, writer)} // Violations - if violationsContent := outputwriter.PolicyViolationsContent(*issuesCollection, writer); len(violationsContent) > 0 { + if violationsContent := outputwriter.PolicyViolationsContent(issuesCollection, writer); len(violationsContent) > 0 { content = append(content, violationsContent...) } // Vulnerabilities diff --git a/utils/consts.go b/utils/consts.go index 3cf30cb86..4562f78bf 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -57,7 +57,9 @@ const ( PathExclusionsEnv = "JF_PATH_EXCLUSIONS" jfrogWatchesEnv = "JF_WATCHES" jfrogProjectEnv = "JF_PROJECT" - ViolationContextEnv = "JF_VIOLATION_CONTEXT" + // To include vulnerabilities and violations + IncludeVulnerabilitiesEnv = "JF_INCLUDE_VULNERABILITIES" + // To include all the vulnerabilities in the source branch at PR scan IncludeAllVulnerabilitiesEnv = "JF_INCLUDE_ALL_VULNERABILITIES" AvoidPreviousPrCommentsDeletionEnv = "JF_AVOID_PREVIOUS_PR_COMMENTS_DELETION" FailOnSecurityIssuesEnv = "JF_FAIL" diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index faacc6de8..dc1cceb52 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -11,6 +11,7 @@ import ( // Group issues by scan type type ScansIssuesCollection struct { formats.ScanStatus + results.ResultContext LicensesViolations []formats.LicenseViolationRow @@ -33,6 +34,8 @@ func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { if issues == nil { return } + // Result context should be the same for all collections + ic.ResultContext = issues.ResultContext // Status ic.AppendStatus(issues.ScanStatus) // Sca @@ -128,10 +131,10 @@ func (ic *ScansIssuesCollection) HasErrors() bool { return false } -func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubScanType, violation bool) map[severityutils.Severity]int { +func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubScanType, vulnerabilities, violation bool) map[severityutils.Severity]int { scanDetails := map[severityutils.Severity]int{} if scanType == utils.ScaScan { - // Count Sca issues + // Count Sca issues only if requested if violation { for _, violation := range ic.ScaViolations { scanDetails[severityutils.GetSeverity(violation.Severity)]++ @@ -139,39 +142,47 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc for _, violation := range ic.LicensesViolations { scanDetails[severityutils.GetSeverity(violation.Severity)]++ } - } else { + } + if vulnerabilities { for _, vulnerability := range ic.ScaVulnerabilities { scanDetails[severityutils.GetSeverity(vulnerability.Severity)]++ } } return scanDetails } - jasIssues := []formats.SourceCodeRow{} + jasVulnerabilities := []formats.SourceCodeRow{} + jasViolations := []formats.SourceCodeRow{} switch scanType { case utils.IacScan: - // Count Iac issues + // Count Iac issues only if requested if violation { - jasIssues = ic.IacViolations - } else { - jasIssues = ic.IacVulnerabilities + jasViolations = ic.IacViolations + } + if vulnerabilities { + jasVulnerabilities = ic.IacVulnerabilities } case utils.SecretsScan: - // Count Secrets issues + // Count Secrets issues only if requested if violation { - jasIssues = ic.SecretsViolations - } else { - jasIssues = ic.SecretsVulnerabilities + jasViolations = ic.SecretsViolations + } + if vulnerabilities { + jasVulnerabilities = ic.SecretsVulnerabilities } case utils.SastScan: - // Count Sast issues + // Count Sast issues only if requested if violation { - jasIssues = ic.SastViolations - } else { - jasIssues = ic.SastVulnerabilities + jasViolations = ic.SastViolations + } + if vulnerabilities { + jasVulnerabilities = ic.SastVulnerabilities } } // Count the issues - for _, issue := range jasIssues { + for _, issue := range jasVulnerabilities { + scanDetails[severityutils.GetSeverity(issue.Severity)]++ + } + for _, issue := range jasViolations { scanDetails[severityutils.GetSeverity(issue.Severity)]++ } return scanDetails diff --git a/utils/issues/issuescollection_test.go b/utils/issues/issuescollection_test.go index 8ea2aee98..dfa44edb1 100644 --- a/utils/issues/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -232,12 +232,13 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { name string scanType utils.SubScanType violation bool + vulnerabilities bool expectedSeverityCount map[string]int }{ { name: "Sca Vulnerabilities", scanType: utils.ScaScan, - violation: false, + vulnerabilities: true, expectedSeverityCount: map[string]int{"High": 1, "Low": 1}, }, { @@ -246,10 +247,17 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { violation: true, expectedSeverityCount: map[string]int{"Critical": 1, "Medium": 1}, }, + { + name: "Sca Vulnerabilities and Violations", + scanType: utils.ScaScan, + vulnerabilities: true, + violation: true, + expectedSeverityCount: map[string]int{"High": 1, "Low": 1, "Critical": 1, "Medium": 1}, + }, { name: "Iac Vulnerabilities", scanType: utils.IacScan, - violation: false, + vulnerabilities: true, expectedSeverityCount: map[string]int{"Low": 1}, }, { @@ -258,10 +266,17 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { violation: true, expectedSeverityCount: map[string]int{}, }, + { + name: "Iac Vulnerabilities and Violations", + scanType: utils.IacScan, + vulnerabilities: true, + violation: true, + expectedSeverityCount: map[string]int{"Low": 1}, + }, { name: "Secrets Vulnerabilities", scanType: utils.SecretsScan, - violation: false, + vulnerabilities: true, expectedSeverityCount: map[string]int{"High": 1}, }, { @@ -270,10 +285,17 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { violation: true, expectedSeverityCount: map[string]int{"High": 1}, }, + { + name: "Secrets Vulnerabilities and Violations", + scanType: utils.SecretsScan, + vulnerabilities: true, + violation: true, + expectedSeverityCount: map[string]int{"High": 2}, + }, { name: "Sast Vulnerabilities", scanType: utils.SastScan, - violation: false, + vulnerabilities: true, expectedSeverityCount: map[string]int{"High": 1, "Unknown": 1}, }, { @@ -282,11 +304,18 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { violation: true, expectedSeverityCount: map[string]int{}, }, + { + name: "Sast Vulnerabilities and Violations", + scanType: utils.SastScan, + vulnerabilities: true, + violation: true, + expectedSeverityCount: map[string]int{"High": 1, "Unknown": 1}, + }, } issuesCollection := getTestData() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - severityCount := issuesCollection.GetScanIssuesSeverityCount(tc.scanType, tc.violation) + severityCount := issuesCollection.GetScanIssuesSeverityCount(tc.scanType, tc.vulnerabilities, tc.violation) assert.Len(t, severityCount, len(tc.expectedSeverityCount)) for severity, count := range tc.expectedSeverityCount { actualCount, ok := severityCount[severityutils.GetSeverity(severity)] diff --git a/utils/outputwriter/markdowntable.go b/utils/outputwriter/markdowntable.go index f4a14b27f..da6c81e6b 100644 --- a/utils/outputwriter/markdowntable.go +++ b/utils/outputwriter/markdowntable.go @@ -36,11 +36,11 @@ type MarkdownTableBuilder struct { type MarkdownColumnType string type MarkdownColumn struct { - Name string - Centered bool - HideIfAllEmpty bool - ColumnType MarkdownColumnType - DefaultValue string + Name string + Centered bool + OmitEmpty bool + ColumnType MarkdownColumnType + DefaultValue string // Internal flag to determine if the column should be hidden shouldHideColumn bool } @@ -144,7 +144,7 @@ func (t *MarkdownTableBuilder) Build() string { // Calculate Hidden columns for c := range t.columns { // Reset shouldHideColumn flag - t.columns[c].shouldHideColumn = t.columns[c].HideIfAllEmpty + t.columns[c].shouldHideColumn = t.columns[c].OmitEmpty } for _, row := range t.rows { for c, cell := range row { diff --git a/utils/outputwriter/markdowntable_test.go b/utils/outputwriter/markdowntable_test.go index 8087150f3..fb8ff113f 100644 --- a/utils/outputwriter/markdowntable_test.go +++ b/utils/outputwriter/markdowntable_test.go @@ -145,10 +145,10 @@ func TestMarkdownTableBuild(t *testing.T) { func TestHideEmptyColumnsInTable(t *testing.T) { columns := []*MarkdownColumn{ - {Name: "col1", HideIfAllEmpty: true}, - {Name: "col2", HideIfAllEmpty: true, Centered: true}, - {Name: "col3", HideIfAllEmpty: false, DefaultValue: "-"}, - {Name: "col4", HideIfAllEmpty: true}, + {Name: "col1", OmitEmpty: true}, + {Name: "col2", OmitEmpty: true, Centered: true}, + {Name: "col3", OmitEmpty: false, DefaultValue: "-"}, + {Name: "col4", OmitEmpty: true}, } testCases := []struct { name string diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 68463552b..8abf776f7 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -157,73 +157,88 @@ func footer(writer OutputWriter) string { return fmt.Sprintf("%s\n%s", SectionDivider(), writer.MarkInCenter(CommentGeneratedByFrogbot)) } -func GetFrogbotErrorCommentContent(contentForComments []string, err error, writer OutputWriter) (comments []string) { - // First decorate with error suffix, then wrap content with the base decorator - return ConvertContentToComments(contentForComments, writer, getFrogbotErrorSuffixDecorator(writer, err), GetFrogbotCommentBaseDecorator(writer)) -} +// func GetFrogbotErrorCommentContent(contentForComments []string, err error, writer OutputWriter) (comments []string) { +// // First decorate with error suffix, then wrap content with the base decorator +// return ConvertContentToComments(contentForComments, writer, getFrogbotErrorSuffixDecorator(writer, err), GetFrogbotCommentBaseDecorator(writer)) +// } // Adding markdown suffix to show the error details and next steps -func getFrogbotErrorSuffixDecorator(writer OutputWriter, err error) CommentDecorator { - return func(_ int, content string) string { - var comment strings.Builder - WriteNewLine(&comment) - // Error - WriteContent(&comment, writer.MarkAsTitle("Error:", 4), err.Error()) - WriteNewLine(&comment) - // Action steps - WriteContent(&comment, - writer.MarkAsTitle("Next Steps:", 4), - "1. Please try to rerun the scan.", - fmt.Sprintf("2. If the issue persists, consider checking the %s for troubleshooting tips.", MarkAsLink("Frogbot documentation", FrogbotDocumentationUrl)), - fmt.Sprintf("3. If you still need assistance, feel free to reach out to %s.", MarkAsLink("JFrog Support", JfrogSupportUrl)), - ) - WriteNewLine(&comment) - WriteContent(&comment, "Thank you for your understanding!") - return content + "\n" + writer.MarkAsDetails(jobErrorTitle, 3, comment.String()) - } -} +// func getFrogbotErrorSuffixDecorator(writer OutputWriter, err error) CommentDecorator { +// return func(_ int, content string) string { +// var comment strings.Builder +// WriteNewLine(&comment) +// // Error +// WriteContent(&comment, writer.MarkAsTitle("Error:", 4), err.Error()) +// WriteNewLine(&comment) +// // Action steps +// WriteContent(&comment, +// writer.MarkAsTitle("Next Steps:", 4), +// "1. Please try to rerun the scan.", +// fmt.Sprintf("2. If the issue persists, consider checking the %s for troubleshooting tips.", MarkAsLink("Frogbot documentation", FrogbotDocumentationUrl)), +// fmt.Sprintf("3. If you still need assistance, feel free to reach out to %s.", MarkAsLink("JFrog Support", JfrogSupportUrl)), +// ) +// WriteNewLine(&comment) +// WriteContent(&comment, "Thank you for your understanding!") +// return content + "\n" + writer.MarkAsDetails(jobErrorTitle, 3, comment.String()) +// } +// } // Summary content -func ScanSummaryContent(issues issues.ScansIssuesCollection, violationContext string, includeSecrets bool, writer OutputWriter) string { +func ScanSummaryContent(issues issues.ScansIssuesCollection, context results.ResultContext, includeSecrets bool, writer OutputWriter) string { if !issues.IssuesExists(includeSecrets) && !issues.HasErrors() { return "" } var contentBuilder strings.Builder - totalIssues := issues.GetTotalVulnerabilities(includeSecrets) - violations := false - if violationContext != "" && violationContext != NoViolations { - totalIssues = issues.GetTotalViolations(includeSecrets) - violations = true + totalIssues := 0 + if issues.HasViolationContext() { + totalIssues += issues.GetTotalViolations(includeSecrets) + } + if issues.IncludeVulnerabilities { + totalIssues += issues.GetTotalVulnerabilities(includeSecrets) } // Title WriteContent(&contentBuilder, writer.MarkAsTitle(scanSummaryTitle, 2)) if issues.HasErrors() { - WriteContent(&contentBuilder, MarkAsBullet(fmt.Sprintf("Frogbot attempted to scan for %s but encountered an error.", violationContext))) + WriteContent(&contentBuilder, MarkAsBullet(fmt.Sprintf("Frogbot attempted to scan for %s but encountered an error.", getResultsContextString(context)))) return contentBuilder.String() } else { - WriteContent(&contentBuilder, MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", violationContext, totalIssues))) + WriteContent(&contentBuilder, MarkAsBullet(fmt.Sprintf("Frogbot scanned for %s and found %d issues", getResultsContextString(context), totalIssues))) } WriteNewLine(&contentBuilder) // Create table, a row for each sub scans summary secretsDetails := "" if includeSecrets { - secretsDetails = getScanSecurityIssuesDetails(issues, utils.SecretsScan, violations, writer) + secretsDetails = getScanSecurityIssuesDetails(issues, context, utils.SecretsScan, writer) } table := NewMarkdownTableWithColumns( NewMarkdownTableSingleValueColumn("Scan Category", "⚠️", false), NewMarkdownTableSingleValueColumn("Status", "⚠️", true), NewMarkdownTableSingleValueColumn("Security Issues", "-", false), ) - table.AddRow(MarkAsBold("Software Composition Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, utils.ScaScan, violations, writer)) + table.AddRow(MarkAsBold("Software Composition Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ScaScan)), getScanSecurityIssuesDetails(issues, context, utils.ScaScan, writer)) table.AddRow(MarkAsBold("Contextual Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ContextualAnalysisScan)), "") - table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.SastScan)), getScanSecurityIssuesDetails(issues, utils.SastScan, violations, writer)) + table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.SastScan)), getScanSecurityIssuesDetails(issues, context, utils.SastScan, writer)) table.AddRow(MarkAsBold("Secrets"), getSubScanResultStatus(issues.GetScanStatus(utils.SecretsScan)), secretsDetails) - table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), getSubScanResultStatus(issues.GetScanStatus(utils.IacScan)), getScanSecurityIssuesDetails(issues, utils.IacScan, violations, writer)) + table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), getSubScanResultStatus(issues.GetScanStatus(utils.IacScan)), getScanSecurityIssuesDetails(issues, context, utils.IacScan, writer)) WriteContent(&contentBuilder, table.Build()) return contentBuilder.String() } +func getResultsContextString(context results.ResultContext) string { + out := "" + if context.HasViolationContext() { + out += "violations" + } + if context.IncludeVulnerabilities { + if out != "" { + out += " and " + } + out += "vulnerabilities" + } + return out +} + func getSubScanResultStatus(scanStatusCode *int) string { if scanStatusCode == nil { return "ℹ️ Not Scanned" @@ -234,21 +249,23 @@ func getSubScanResultStatus(scanStatusCode *int) string { return "❌ Failed" } -func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, scanType utils.SubScanType, violation bool, writer OutputWriter) string { +func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, context results.ResultContext, scanType utils.SubScanType, writer OutputWriter) string { if issues.HasErrors() || issues.IsScanNotCompleted(scanType) { // Failed/Not scanned, no need to show the details return "" } var severityCountMap map[severityutils.Severity]int + countViolations := context.HasViolationContext() + countVulnerabilities := context.IncludeVulnerabilities switch scanType { case utils.ScaScan: - severityCountMap = issues.GetScanIssuesSeverityCount(utils.ScaScan, violation) + severityCountMap = issues.GetScanIssuesSeverityCount(utils.ScaScan, countVulnerabilities, countViolations) case utils.SastScan: - severityCountMap = issues.GetScanIssuesSeverityCount(utils.SastScan, violation) + severityCountMap = issues.GetScanIssuesSeverityCount(utils.SastScan, countVulnerabilities, countViolations) case utils.SecretsScan: - severityCountMap = issues.GetScanIssuesSeverityCount(utils.SecretsScan, violation) + severityCountMap = issues.GetScanIssuesSeverityCount(utils.SecretsScan, countVulnerabilities, countViolations) case utils.IacScan: - severityCountMap = issues.GetScanIssuesSeverityCount(utils.IacScan, violation) + severityCountMap = issues.GetScanIssuesSeverityCount(utils.IacScan, countVulnerabilities, countViolations) } totalIssues := getTotalIssues(severityCountMap) if totalIssues == 0 { @@ -553,10 +570,10 @@ func getSecretsDescriptionTable(writer OutputWriter, issues ...formats.SourceCod // Construct table table := NewMarkdownTable("Severity", "ID", "Status", "Finding", "Watch Name", "Policies").SetDelimiter(writer.Separator()) // Hide optional columns if all empty (violations/no status) - table.GetColumnInfo("ID").HideIfAllEmpty = true - table.GetColumnInfo("Status").HideIfAllEmpty = true - table.GetColumnInfo("Watch Name").HideIfAllEmpty = true - table.GetColumnInfo("Policies").HideIfAllEmpty = true + table.GetColumnInfo("ID").OmitEmpty = true + table.GetColumnInfo("Status").OmitEmpty = true + table.GetColumnInfo("Watch Name").OmitEmpty = true + table.GetColumnInfo("Policies").OmitEmpty = true // Construct rows for _, issue := range issues { // Determine the issue applicable status @@ -729,9 +746,9 @@ func getJasIssueDescriptionTable(writer OutputWriter, issues ...formats.SourceCo // Construct table table := NewMarkdownTable("Severity", "ID", "Finding", "Watch Name", "Policies").SetDelimiter(writer.Separator()) // Hide optional columns if all empty (not violations) - table.GetColumnInfo("ID").HideIfAllEmpty = true - table.GetColumnInfo("Watch Name").HideIfAllEmpty = true - table.GetColumnInfo("Policies").HideIfAllEmpty = true + table.GetColumnInfo("ID").OmitEmpty = true + table.GetColumnInfo("Watch Name").OmitEmpty = true + table.GetColumnInfo("Policies").OmitEmpty = true // Construct rows for _, issue := range issues { table.AddRowWithCellData( diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index d2a81479f..df71f11fc 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -1,15 +1,16 @@ package outputwriter import ( - "fmt" "path/filepath" "testing" "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/froggit-go/vcsutils" + xrayApi "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/stretchr/testify/assert" ) @@ -233,7 +234,7 @@ func TestScanSummaryContent(t *testing.T) { testCases := []struct { name string - violationContext string + context results.ResultContext includeSecrets bool scanStatus formats.ScanStatus issues issues.ScansIssuesCollection @@ -257,9 +258,10 @@ func TestScanSummaryContent(t *testing.T) { }, }, { - name: "With vulnerabilities", + name: "Vulnerabilities", issues: testIssues, scanStatus: testScanStatus, + context: results.ResultContext{GitRepoHttpsCloneUrl: "url", IncludeVulnerabilities: true}, cases: []OutputTestCase{ { name: "Standard output", @@ -274,9 +276,10 @@ func TestScanSummaryContent(t *testing.T) { }, }, { - name: "With violation context", + name: "Violations", issues: testIssues, scanStatus: testScanStatus, + context: results.ResultContext{Watches: []string{"watch"}}, cases: []OutputTestCase{ { name: "Standard output", @@ -291,72 +294,52 @@ func TestScanSummaryContent(t *testing.T) { }, }, { - name: "with errors", - issues: issues.ScansIssuesCollection{}, - scanStatus: formats.ScanStatus{ - IacStatusCode: utils.NewIntPtr(33), - }, + name: "Violations and Vulnerabilities", + issues: testIssues, + scanStatus: testScanStatus, + context: results.ResultContext{GitRepoHttpsCloneUrl: "url", PlatformWatches: &xrayApi.ResourcesWatchesBody{GitRepositoryWatches: []string{"watch"}}, IncludeVulnerabilities: true}, cases: []OutputTestCase{ { name: "Standard output", writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_error_standard.md")}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_both_standard.md")}, }, { name: "Simplified output", writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_error_simplified.md")}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_both_simplified.md")}, }, }, }, - } - - for _, tc := range testCases { - for _, test := range tc.cases { - t.Run(tc.name+"_"+test.name, func(t *testing.T) { - expectedOutput := GetExpectedTestOutput(t, test) - tc.issues.ScanStatus = tc.scanStatus - output := ScanSummaryContent(tc.issues, tc.violationContext, tc.includeSecrets, test.writer) - assert.Equal(t, expectedOutput, output) - }) - } - } -} - -func TestGetFrogbotErrorCommentContent(t *testing.T) { - testCases := []struct { - name string - cases []OutputTestCase - issues issues.ScansIssuesCollection - }{ { - name: "error details", - issues: issues.ScansIssuesCollection{ - ScanStatus: formats.ScanStatus{ - IacStatusCode: utils.NewIntPtr(33), - }, + name: "with errors", + issues: issues.ScansIssuesCollection{}, + scanStatus: formats.ScanStatus{ + IacStatusCode: utils.NewIntPtr(33), }, + context: results.ResultContext{GitRepoHttpsCloneUrl: "url", PlatformWatches: &xrayApi.ResourcesWatchesBody{GitRepositoryWatches: []string{"watch"}}}, cases: []OutputTestCase{ { name: "Standard output", writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "structure", "error_standard.md")}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_error_standard.md")}, }, { name: "Simplified output", writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, - expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "structure", "error_simplified.md")}, + expectedOutputPath: []string{filepath.Join(testSummaryCommentDir, "summary", "summary_error_simplified.md")}, }, }, }, } + for _, tc := range testCases { for _, test := range tc.cases { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) - output := GetFrogbotErrorCommentContent([]string{ScanSummaryContent(tc.issues, "", false, test.writer)}, fmt.Errorf("Some error that occurred in scan"), test.writer) - assert.Len(t, output, 1) - assert.Equal(t, expectedOutput, output[0]) + tc.issues.ScanStatus = tc.scanStatus + output := ScanSummaryContent(tc.issues, tc.context, tc.includeSecrets, test.writer) + assert.Equal(t, expectedOutput, output) }) } } diff --git a/utils/params.go b/utils/params.go index ef19fa503..42fda1980 100644 --- a/utils/params.go +++ b/utils/params.go @@ -276,8 +276,8 @@ func (s *Scan) setDefaultsIfNeeded() (err error) { type JFrogPlatform struct { XrayVersion string XscVersion string - ViolationContext ViolationContext `yaml:"violationContext,omitempty"` Watches []string `yaml:"watches,omitempty"` + IncludeVulnerabilities bool `yaml:"includeVulnerabilities,omitempty"` JFrogProjectKey string `yaml:"jfrogProjectKey,omitempty"` } @@ -295,20 +295,11 @@ func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { // We don't want to return an error from this function if the error is of type ErrMissingEnv because JFrogPlatform environment variables are not mandatory. err = nil } - if jp.ViolationContext == None { - jp.ViolationContext = ViolationContext(getTrimmedEnv(ViolationContextEnv)) - } - // Validate or set Default base on other params - if jp.ViolationContext == None { - if len(jp.Watches) > 0 { - jp.ViolationContext = WatchContext - } else if jp.JFrogProjectKey != "" { - jp.ViolationContext = ProjectContext + if !jp.IncludeVulnerabilities { + if jp.IncludeVulnerabilities, err = getBoolEnv(IncludeVulnerabilitiesEnv, false); err != nil { + return } } - if jp.ViolationContext == None { - jp.ViolationContext = GitRepoContext - } return } @@ -324,6 +315,7 @@ type Git struct { PullRequestTitleTemplate string `yaml:"pullRequestTitleTemplate,omitempty"` PullRequestCommentTitle string `yaml:"pullRequestCommentTitle,omitempty"` PullRequestSecretComments bool `yaml:"pullRequestSecretComments,omitempty"` + PullRequestDisableErrorComment bool `yaml:"pullRequestDisableErrorComment,omitempty"` AvoidExtraMessages bool `yaml:"avoidExtraMessages,omitempty"` EmailAuthor string `yaml:"emailAuthor,omitempty"` AggregateFixes bool `yaml:"aggregateFixes,omitempty"` diff --git a/utils/scandetails.go b/utils/scandetails.go index 15d3671ed..80f2d7fca 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -3,7 +3,7 @@ package utils import ( "context" "fmt" - "os" + // "os" "path/filepath" "time" @@ -11,12 +11,12 @@ import ( "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + // "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-security/commands/audit" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" - "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" + // "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" @@ -25,7 +25,8 @@ import ( type ScanDetails struct { *Project *Git - *services.XrayGraphScanParams + + // *services.XrayGraphScanParams *xscservices.XscGitInfoContext *config.ServerDetails client vcsclient.VcsClient @@ -37,6 +38,11 @@ type ScanDetails struct { baseBranch string configProfile *clientservices.ConfigProfile allowPartialResults bool + + results.ResultContext + MultiScanId string + XrayVersion string + XscVersion string StartTime time.Time } @@ -59,11 +65,16 @@ func (sc *ScanDetails) SetProject(project *Project) *ScanDetails { return sc } -func (sc *ScanDetails) SetXrayGraphScanParams(httpCloneUrl string, watches []string, jfrogProjectKey string, includeLicenses bool) *ScanDetails { - sc.XrayGraphScanParams = createXrayScanParams(httpCloneUrl, watches, jfrogProjectKey, includeLicenses) +func (sc *ScanDetails) SetResultsContext(httpCloneUrl string, watches []string, jfrogProjectKey string, includeVulnerabilities, includeLicenses bool) *ScanDetails { + sc.ResultContext = audit.CreateResultsContext(sc.ServerDetails, sc.XrayVersion, watches, sc.RepoPath, jfrogProjectKey, httpCloneUrl, includeVulnerabilities, includeLicenses) return sc } +// func (sc *ScanDetails) SetXrayGraphScanParams(httpCloneUrl string, watches []string, jfrogProjectKey string, includeVulnerabilities, includeLicenses bool) *ScanDetails { +// sc.XrayGraphScanParams = createXrayScanParams(httpCloneUrl, watches, jfrogProjectKey, includeVulnerabilities, includeLicenses) +// return sc +// } + func (sc *ScanDetails) SetFixableOnly(fixable bool) *ScanDetails { sc.fixableOnly = fixable return sc @@ -139,55 +150,15 @@ func (sc *ScanDetails) AllowPartialResults() bool { return sc.allowPartialResults } -func (sc *ScanDetails) CreateCommonGraphScanParams() *scangraph.CommonGraphScanParams { - commonParams := &scangraph.CommonGraphScanParams{ - RepoPath: sc.RepoPath, - Watches: sc.Watches, - GitRepoHttpsCloneUrl: sc.Git.RepositoryCloneUrl, - ScanType: sc.ScanType, - } - if sc.ProjectKey == "" { - commonParams.ProjectKey = os.Getenv(coreutils.Project) - } else { - commonParams.ProjectKey = sc.ProjectKey - } - commonParams.IncludeVulnerabilities = sc.IncludeVulnerabilities - commonParams.IncludeLicenses = sc.IncludeLicenses - return commonParams -} - -func (sc *ScanDetails) HasViolationContext() bool { - // TODO: infer this info from the results of the scan and delete this method. - return sc.ProjectKey != "" || sc.Git.RepositoryCloneUrl != "" || len(sc.Watches) > 0 || sc.RepoPath != "" -} - -func createXrayScanParams(httpCloneUrl string, watches []string, project string, includeLicenses bool) (params *services.XrayGraphScanParams) { - params = &services.XrayGraphScanParams{ - ScanType: services.Dependency, +func createXrayScanParams(httpCloneUrl string, watches []string, project string, includeVulnerabilities, includeLicenses bool) (params *services.XrayGraphScanParams) { + return &services.XrayGraphScanParams{ + Watches: watches, + ProjectKey: project, + GitRepoHttpsCloneUrl: httpCloneUrl, + IncludeVulnerabilities: includeVulnerabilities, IncludeLicenses: includeLicenses, + ScanType: services.Dependency, } - defer func() { - log.Debug(fmt.Sprintf("Passing XrayGraphScanParams: %+v", params)) - }() - if len(httpCloneUrl) > 0 { - // We always pass the clone URL to the platform, with other parameters combined. - // the logic to decide whether to use it or not is in the audit command. - params.GitRepoHttpsCloneUrl = httpCloneUrl - } - if len(watches) > 0 { - params.Watches = watches - return - } - if project != "" { - params.ProjectKey = project - return - } - // TODO: --vuln flag, should be able to get both vulnerabilities and violations. - // since if watch is defined on git-repo resource there is no way to get vulnerabilities. - if len(httpCloneUrl) == 0 { - params.IncludeVulnerabilities = true - } - return } func (sc *ScanDetails) RunInstallAndAudit(workDirs ...string) (auditResults *results.SecurityCommandResults) { @@ -213,7 +184,7 @@ func (sc *ScanDetails) RunInstallAndAudit(workDirs ...string) (auditResults *res SetMinSeverityFilter(sc.MinSeverityFilter()). SetFixableOnly(sc.FixableOnly()). SetGraphBasicParams(auditBasicParams). - SetCommonGraphScanParams(sc.CreateCommonGraphScanParams()). + SetResultsContext(sc.ResultContext). SetConfigProfile(sc.configProfile). SetMultiScanId(sc.MultiScanId). SetStartTime(sc.StartTime) diff --git a/utils/scandetails_test.go b/utils/scandetails_test.go index f685c7f9e..462ace840 100644 --- a/utils/scandetails_test.go +++ b/utils/scandetails_test.go @@ -6,45 +6,67 @@ import ( "testing" ) -func TestCreateXrayScanParams(t *testing.T) { - // Project - scanDetails := &ScanDetails{} - scanDetails.SetXrayGraphScanParams("", nil, "", false) - assert.Empty(t, scanDetails.Watches) - assert.Equal(t, "", scanDetails.ProjectKey) - assert.True(t, scanDetails.IncludeVulnerabilities) - assert.False(t, scanDetails.IncludeLicenses) - - // Watches - scanDetails.SetXrayGraphScanParams("", []string{"watch-1", "watch-2"}, "", false) - assert.Equal(t, []string{"watch-1", "watch-2"}, scanDetails.Watches) - assert.Equal(t, "", scanDetails.ProjectKey) - assert.False(t, scanDetails.IncludeVulnerabilities) - assert.False(t, scanDetails.IncludeLicenses) - - // Project - scanDetails.SetXrayGraphScanParams("", nil, "project", true) - assert.Empty(t, scanDetails.Watches) - assert.Equal(t, "project", scanDetails.ProjectKey) - assert.False(t, scanDetails.IncludeVulnerabilities) - assert.True(t, scanDetails.IncludeLicenses) - - // GitInfoContext - scanDetails.SetXrayGraphScanParams("http://localhost:8080/my-user/my-project.git", nil, "", false) - assert.Empty(t, scanDetails.Watches) - assert.Equal(t, "", scanDetails.ProjectKey) - assert.False(t, scanDetails.IncludeVulnerabilities) - assert.False(t, scanDetails.IncludeLicenses) - assert.NotNil(t, scanDetails.XscGitInfoContext) - assert.Equal(t, "http://localhost:8080/my-user/my-project.git", scanDetails.XscGitInfoContext.GitRepoHttpsCloneUrl) - - // Vulnerabilities - scanDetails.SetXrayGraphScanParams("", nil, "", true) - assert.Empty(t, scanDetails.Watches) - assert.Equal(t, "", scanDetails.ProjectKey) - assert.True(t, scanDetails.IncludeVulnerabilities) - assert.True(t, scanDetails.IncludeLicenses) - assert.Nil(t, scanDetails.XscGitInfoContext) +func TestCreateResultsContext(t *testing.T) { + testCases := []struct { + name string + httpCloneUrl string + watches []string + jfrogProjectKey string + includeVulnerabilities bool + includeLicenses bool + }{ + { + name: "Violations and Vulnerabilities", + httpCloneUrl: "http://localhost:8080/my-user/my-project.git", + watches: []string{"watch-1", "watch-2"}, + jfrogProjectKey: "project", + includeVulnerabilities: true, + includeLicenses: true, + }, + { + name: "Violations - Project key", + httpCloneUrl: "", + watches: nil, + jfrogProjectKey: "project", + includeVulnerabilities: false, + includeLicenses: true, + }, + { + name: "Violations - Watches", + httpCloneUrl: "", + watches: []string{"watch-1", "watch-2"}, + jfrogProjectKey: "", + includeVulnerabilities: false, + includeLicenses: false, + }, + { + name: "Violations - GitInfoContext", + httpCloneUrl: "http://localhost:8080/my-user/my-project.git", + watches: nil, + jfrogProjectKey: "", + includeVulnerabilities: false, + includeLicenses: false, + }, + { + name: "Vulnerabilities", + httpCloneUrl: "", + watches: nil, + jfrogProjectKey: "", + includeVulnerabilities: true, + includeLicenses: true, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + scanDetails := &ScanDetails{} + scanDetails.SetResultsContext(testCase.httpCloneUrl, testCase.watches, testCase.jfrogProjectKey, testCase.includeVulnerabilities, testCase.includeLicenses) + assert.Equal(t, testCase.httpCloneUrl, scanDetails.XscGitInfoContext.GitRepoHttpsCloneUrl) + assert.Equal(t, testCase.watches, scanDetails.Watches) + assert.Equal(t, testCase.jfrogProjectKey, scanDetails.ProjectKey) + assert.Equal(t, testCase.includeVulnerabilities, scanDetails.IncludeVulnerabilities) + assert.Equal(t, testCase.includeLicenses, scanDetails.IncludeLicenses) + }) + } } func TestGetFullPathWorkingDirs(t *testing.T) { diff --git a/utils/utils.go b/utils/utils.go index 2351ed90a..a68f17231 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -51,14 +51,15 @@ const ( ) // ViolationContext is a type for violation context (None,Project,GitRepo) -const ( - None ViolationContext = "" // No violation context - WatchContext ViolationContext = "watch" - ProjectContext ViolationContext = "project" - GitRepoContext ViolationContext = "git" -) +// const ( +// None ViolationContext = "" // No violation context +// WatchContext ViolationContext = "watch" +// ProjectContext ViolationContext = "project" +// GitRepoContext ViolationContext = "git" +// ) + +// type ViolationContext string -type ViolationContext string var ( TrueVal = true @@ -233,8 +234,8 @@ func VulnerabilityDetailsToMD5Hash(vulnerabilities ...formats.VulnerabilityOrVio return hex.EncodeToString(hash.Sum(nil)), nil } -func UploadSarifResultsToGithubSecurityTab(scanResults *results.SecurityCommandResults, repo *Repository, branch string, client vcsclient.VcsClient, includeVulnerabilities, hasViolationContext bool) error { - report, err := GenerateFrogbotSarifReport(scanResults, scanResults.HasMultipleTargets(), includeVulnerabilities, hasViolationContext, repo.AllowedLicenses) +func UploadSarifResultsToGithubSecurityTab(scanResults *results.SecurityCommandResults, repo *Repository, branch string, client vcsclient.VcsClient) error { + report, err := GenerateFrogbotSarifReport(scanResults, repo.AllowedLicenses) if err != nil { return err } @@ -246,11 +247,10 @@ func UploadSarifResultsToGithubSecurityTab(scanResults *results.SecurityCommandR return nil } -func GenerateFrogbotSarifReport(extendedResults *results.SecurityCommandResults, isMultipleRoots, includeVulnerabilities, hasViolationContext bool, allowedLicenses []string) (string, error) { +func GenerateFrogbotSarifReport(extendedResults *results.SecurityCommandResults, allowedLicenses []string) (string, error) { convertor := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{ - IncludeVulnerabilities: includeVulnerabilities, - HasViolationContext: hasViolationContext, - IsMultipleRoots: &isMultipleRoots, + IncludeVulnerabilities: extendedResults.IncludesVulnerabilities(), + HasViolationContext: extendedResults.HasViolationContext(), AllowedLicenses: allowedLicenses, }) sarifReport, err := convertor.ConvertToSarif(extendedResults) From f11f066eaa3ddd0ac7f6e100c1a78c46516f4f66 Mon Sep 17 00:00:00 2001 From: attiasas Date: Fri, 20 Dec 2024 18:15:10 +0300 Subject: [PATCH 25/34] format --- scanpullrequest/scanpullrequest.go | 2 +- scanrepository/scanrepository_test.go | 8 ++-- utils/consts.go | 14 +++--- utils/issues/issuescollection.go | 8 ++-- utils/issues/issuescollection_test.go | 26 +++++----- utils/outputwriter/outputcontent_test.go | 20 ++++---- utils/params.go | 42 ++++++++--------- utils/scandetails.go | 16 +++---- utils/scandetails_test.go | 60 ++++++++++++------------ utils/utils.go | 1 - 10 files changed, 98 insertions(+), 99 deletions(-) diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index cf1866a2d..c5fe63591 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -306,7 +306,7 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] return nil, err } return &issues.ScansIssuesCollection{ - ResultContext: cmdResults.ResultContext, + ResultContext: cmdResults.ResultContext, ScanStatus: simpleJsonResults.Statuses, ScaVulnerabilities: simpleJsonResults.Vulnerabilities, ScaViolations: simpleJsonResults.SecurityViolations, diff --git a/scanrepository/scanrepository_test.go b/scanrepository/scanrepository_test.go index 3748955b2..e795d8aeb 100644 --- a/scanrepository/scanrepository_test.go +++ b/scanrepository/scanrepository_test.go @@ -435,8 +435,8 @@ func TestPackageTypeFromScan(t *testing.T) { frogbotParams.Projects[0].InstallCommandName = pkg.commandName frogbotParams.Projects[0].InstallCommandArgs = pkg.commandArgs scanSetup := utils.ScanDetails{ - XrayVersion: xrayVersion, - XscVersion: xscVersion, + XrayVersion: xrayVersion, + XscVersion: xscVersion, Project: &frogbotParams.Projects[0], ServerDetails: &frogbotParams.Server, } @@ -550,7 +550,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { Violations: []services.Violation{ { ViolationType: "security", - WatchName: "w1", + WatchName: "w1", Cves: []services.Cve{ {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, @@ -565,7 +565,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { }, { ViolationType: "security", - WatchName: "w1", + WatchName: "w1", Cves: []services.Cve{ {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, diff --git a/utils/consts.go b/utils/consts.go index 4562f78bf..b254e8b85 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -51,14 +51,14 @@ const ( PullRequestSecretCommentsEnv = "JF_PR_SHOW_SECRETS_COMMENTS" // Repository environment variables - Ignored if the frogbot-config.yml file is used - InstallCommandEnv = "JF_INSTALL_DEPS_CMD" - RequirementsFileEnv = "JF_REQUIREMENTS_FILE" - WorkingDirectoryEnv = "JF_WORKING_DIR" - PathExclusionsEnv = "JF_PATH_EXCLUSIONS" - jfrogWatchesEnv = "JF_WATCHES" - jfrogProjectEnv = "JF_PROJECT" + InstallCommandEnv = "JF_INSTALL_DEPS_CMD" + RequirementsFileEnv = "JF_REQUIREMENTS_FILE" + WorkingDirectoryEnv = "JF_WORKING_DIR" + PathExclusionsEnv = "JF_PATH_EXCLUSIONS" + jfrogWatchesEnv = "JF_WATCHES" + jfrogProjectEnv = "JF_PROJECT" // To include vulnerabilities and violations - IncludeVulnerabilitiesEnv = "JF_INCLUDE_VULNERABILITIES" + IncludeVulnerabilitiesEnv = "JF_INCLUDE_VULNERABILITIES" // To include all the vulnerabilities in the source branch at PR scan IncludeAllVulnerabilitiesEnv = "JF_INCLUDE_ALL_VULNERABILITIES" AvoidPreviousPrCommentsDeletionEnv = "JF_AVOID_PREVIOUS_PR_COMMENTS_DELETION" diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index dc1cceb52..dadb0c32b 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -142,7 +142,7 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc for _, violation := range ic.LicensesViolations { scanDetails[severityutils.GetSeverity(violation.Severity)]++ } - } + } if vulnerabilities { for _, vulnerability := range ic.ScaVulnerabilities { scanDetails[severityutils.GetSeverity(vulnerability.Severity)]++ @@ -157,7 +157,7 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc // Count Iac issues only if requested if violation { jasViolations = ic.IacViolations - } + } if vulnerabilities { jasVulnerabilities = ic.IacVulnerabilities } @@ -165,7 +165,7 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc // Count Secrets issues only if requested if violation { jasViolations = ic.SecretsViolations - } + } if vulnerabilities { jasVulnerabilities = ic.SecretsVulnerabilities } @@ -173,7 +173,7 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc // Count Sast issues only if requested if violation { jasViolations = ic.SastViolations - } + } if vulnerabilities { jasVulnerabilities = ic.SastVulnerabilities } diff --git a/utils/issues/issuescollection_test.go b/utils/issues/issuescollection_test.go index dfa44edb1..3ae68d714 100644 --- a/utils/issues/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -232,13 +232,13 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { name string scanType utils.SubScanType violation bool - vulnerabilities bool + vulnerabilities bool expectedSeverityCount map[string]int }{ { name: "Sca Vulnerabilities", scanType: utils.ScaScan, - vulnerabilities: true, + vulnerabilities: true, expectedSeverityCount: map[string]int{"High": 1, "Low": 1}, }, { @@ -248,16 +248,16 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { expectedSeverityCount: map[string]int{"Critical": 1, "Medium": 1}, }, { - name: "Sca Vulnerabilities and Violations", + name: "Sca Vulnerabilities and Violations", scanType: utils.ScaScan, - vulnerabilities: true, + vulnerabilities: true, violation: true, expectedSeverityCount: map[string]int{"High": 1, "Low": 1, "Critical": 1, "Medium": 1}, }, { name: "Iac Vulnerabilities", scanType: utils.IacScan, - vulnerabilities: true, + vulnerabilities: true, expectedSeverityCount: map[string]int{"Low": 1}, }, { @@ -267,16 +267,16 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { expectedSeverityCount: map[string]int{}, }, { - name: "Iac Vulnerabilities and Violations", + name: "Iac Vulnerabilities and Violations", scanType: utils.IacScan, - vulnerabilities: true, + vulnerabilities: true, violation: true, expectedSeverityCount: map[string]int{"Low": 1}, }, { name: "Secrets Vulnerabilities", scanType: utils.SecretsScan, - vulnerabilities: true, + vulnerabilities: true, expectedSeverityCount: map[string]int{"High": 1}, }, { @@ -286,16 +286,16 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { expectedSeverityCount: map[string]int{"High": 1}, }, { - name: "Secrets Vulnerabilities and Violations", + name: "Secrets Vulnerabilities and Violations", scanType: utils.SecretsScan, - vulnerabilities: true, + vulnerabilities: true, violation: true, expectedSeverityCount: map[string]int{"High": 2}, }, { name: "Sast Vulnerabilities", scanType: utils.SastScan, - vulnerabilities: true, + vulnerabilities: true, expectedSeverityCount: map[string]int{"High": 1, "Unknown": 1}, }, { @@ -305,9 +305,9 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { expectedSeverityCount: map[string]int{}, }, { - name: "Sast Vulnerabilities and Violations", + name: "Sast Vulnerabilities and Violations", scanType: utils.SastScan, - vulnerabilities: true, + vulnerabilities: true, violation: true, expectedSeverityCount: map[string]int{"High": 1, "Unknown": 1}, }, diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index df71f11fc..51a0b1f4c 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -6,12 +6,12 @@ import ( "github.com/jfrog/frogbot/v2/utils/issues" "github.com/jfrog/froggit-go/vcsutils" - xrayApi "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/jasutils" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" + xrayApi "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/stretchr/testify/assert" ) @@ -233,12 +233,12 @@ func TestScanSummaryContent(t *testing.T) { } testCases := []struct { - name string - context results.ResultContext - includeSecrets bool - scanStatus formats.ScanStatus - issues issues.ScansIssuesCollection - cases []OutputTestCase + name string + context results.ResultContext + includeSecrets bool + scanStatus formats.ScanStatus + issues issues.ScansIssuesCollection + cases []OutputTestCase }{ { name: "No issues", @@ -261,7 +261,7 @@ func TestScanSummaryContent(t *testing.T) { name: "Vulnerabilities", issues: testIssues, scanStatus: testScanStatus, - context: results.ResultContext{GitRepoHttpsCloneUrl: "url", IncludeVulnerabilities: true}, + context: results.ResultContext{GitRepoHttpsCloneUrl: "url", IncludeVulnerabilities: true}, cases: []OutputTestCase{ { name: "Standard output", @@ -279,7 +279,7 @@ func TestScanSummaryContent(t *testing.T) { name: "Violations", issues: testIssues, scanStatus: testScanStatus, - context: results.ResultContext{Watches: []string{"watch"}}, + context: results.ResultContext{Watches: []string{"watch"}}, cases: []OutputTestCase{ { name: "Standard output", @@ -297,7 +297,7 @@ func TestScanSummaryContent(t *testing.T) { name: "Violations and Vulnerabilities", issues: testIssues, scanStatus: testScanStatus, - context: results.ResultContext{GitRepoHttpsCloneUrl: "url", PlatformWatches: &xrayApi.ResourcesWatchesBody{GitRepositoryWatches: []string{"watch"}}, IncludeVulnerabilities: true}, + context: results.ResultContext{GitRepoHttpsCloneUrl: "url", PlatformWatches: &xrayApi.ResourcesWatchesBody{GitRepositoryWatches: []string{"watch"}}, IncludeVulnerabilities: true}, cases: []OutputTestCase{ { name: "Standard output", diff --git a/utils/params.go b/utils/params.go index 42fda1980..20111a5e9 100644 --- a/utils/params.go +++ b/utils/params.go @@ -274,11 +274,11 @@ func (s *Scan) setDefaultsIfNeeded() (err error) { } type JFrogPlatform struct { - XrayVersion string - XscVersion string - Watches []string `yaml:"watches,omitempty"` - IncludeVulnerabilities bool `yaml:"includeVulnerabilities,omitempty"` - JFrogProjectKey string `yaml:"jfrogProjectKey,omitempty"` + XrayVersion string + XscVersion string + Watches []string `yaml:"watches,omitempty"` + IncludeVulnerabilities bool `yaml:"includeVulnerabilities,omitempty"` + JFrogProjectKey string `yaml:"jfrogProjectKey,omitempty"` } func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { @@ -306,22 +306,22 @@ func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { type Git struct { GitProvider vcsutils.VcsProvider vcsclient.VcsInfo - UseMostCommonAncestorAsTarget bool `yaml:"useMostCommonAncestorAsTarget,omitempty"` - RepoOwner string - RepoName string `yaml:"repoName,omitempty"` - Branches []string `yaml:"branches,omitempty"` - BranchNameTemplate string `yaml:"branchNameTemplate,omitempty"` - CommitMessageTemplate string `yaml:"commitMessageTemplate,omitempty"` - PullRequestTitleTemplate string `yaml:"pullRequestTitleTemplate,omitempty"` - PullRequestCommentTitle string `yaml:"pullRequestCommentTitle,omitempty"` - PullRequestSecretComments bool `yaml:"pullRequestSecretComments,omitempty"` - PullRequestDisableErrorComment bool `yaml:"pullRequestDisableErrorComment,omitempty"` - AvoidExtraMessages bool `yaml:"avoidExtraMessages,omitempty"` - EmailAuthor string `yaml:"emailAuthor,omitempty"` - AggregateFixes bool `yaml:"aggregateFixes,omitempty"` - PullRequestDetails vcsclient.PullRequestInfo - RepositoryCloneUrl string - UseLocalRepository bool + UseMostCommonAncestorAsTarget bool `yaml:"useMostCommonAncestorAsTarget,omitempty"` + RepoOwner string + RepoName string `yaml:"repoName,omitempty"` + Branches []string `yaml:"branches,omitempty"` + BranchNameTemplate string `yaml:"branchNameTemplate,omitempty"` + CommitMessageTemplate string `yaml:"commitMessageTemplate,omitempty"` + PullRequestTitleTemplate string `yaml:"pullRequestTitleTemplate,omitempty"` + PullRequestCommentTitle string `yaml:"pullRequestCommentTitle,omitempty"` + PullRequestSecretComments bool `yaml:"pullRequestSecretComments,omitempty"` + PullRequestDisableErrorComment bool `yaml:"pullRequestDisableErrorComment,omitempty"` + AvoidExtraMessages bool `yaml:"avoidExtraMessages,omitempty"` + EmailAuthor string `yaml:"emailAuthor,omitempty"` + AggregateFixes bool `yaml:"aggregateFixes,omitempty"` + PullRequestDetails vcsclient.PullRequestInfo + RepositoryCloneUrl string + UseLocalRepository bool } func (g *Git) setDefaultsIfNeeded(gitParamsFromEnv *Git, commandName string) (err error) { diff --git a/utils/scandetails.go b/utils/scandetails.go index 80f2d7fca..9d673331a 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -25,7 +25,7 @@ import ( type ScanDetails struct { *Project *Git - + // *services.XrayGraphScanParams *xscservices.XscGitInfoContext *config.ServerDetails @@ -42,8 +42,8 @@ type ScanDetails struct { results.ResultContext MultiScanId string XrayVersion string - XscVersion string - StartTime time.Time + XscVersion string + StartTime time.Time } func NewScanDetails(client vcsclient.VcsClient, server *config.ServerDetails, git *Git) *ScanDetails { @@ -152,12 +152,12 @@ func (sc *ScanDetails) AllowPartialResults() bool { func createXrayScanParams(httpCloneUrl string, watches []string, project string, includeVulnerabilities, includeLicenses bool) (params *services.XrayGraphScanParams) { return &services.XrayGraphScanParams{ - Watches: watches, - ProjectKey: project, - GitRepoHttpsCloneUrl: httpCloneUrl, + Watches: watches, + ProjectKey: project, + GitRepoHttpsCloneUrl: httpCloneUrl, IncludeVulnerabilities: includeVulnerabilities, - IncludeLicenses: includeLicenses, - ScanType: services.Dependency, + IncludeLicenses: includeLicenses, + ScanType: services.Dependency, } } diff --git a/utils/scandetails_test.go b/utils/scandetails_test.go index 462ace840..5617350ce 100644 --- a/utils/scandetails_test.go +++ b/utils/scandetails_test.go @@ -8,52 +8,52 @@ import ( func TestCreateResultsContext(t *testing.T) { testCases := []struct { - name string - httpCloneUrl string - watches []string - jfrogProjectKey string + name string + httpCloneUrl string + watches []string + jfrogProjectKey string includeVulnerabilities bool - includeLicenses bool + includeLicenses bool }{ { - name: "Violations and Vulnerabilities", - httpCloneUrl: "http://localhost:8080/my-user/my-project.git", - watches: []string{"watch-1", "watch-2"}, - jfrogProjectKey: "project", + name: "Violations and Vulnerabilities", + httpCloneUrl: "http://localhost:8080/my-user/my-project.git", + watches: []string{"watch-1", "watch-2"}, + jfrogProjectKey: "project", includeVulnerabilities: true, - includeLicenses: true, + includeLicenses: true, }, { - name: "Violations - Project key", - httpCloneUrl: "", - watches: nil, - jfrogProjectKey: "project", + name: "Violations - Project key", + httpCloneUrl: "", + watches: nil, + jfrogProjectKey: "project", includeVulnerabilities: false, - includeLicenses: true, + includeLicenses: true, }, { - name: "Violations - Watches", - httpCloneUrl: "", - watches: []string{"watch-1", "watch-2"}, - jfrogProjectKey: "", + name: "Violations - Watches", + httpCloneUrl: "", + watches: []string{"watch-1", "watch-2"}, + jfrogProjectKey: "", includeVulnerabilities: false, - includeLicenses: false, + includeLicenses: false, }, { - name: "Violations - GitInfoContext", - httpCloneUrl: "http://localhost:8080/my-user/my-project.git", - watches: nil, - jfrogProjectKey: "", + name: "Violations - GitInfoContext", + httpCloneUrl: "http://localhost:8080/my-user/my-project.git", + watches: nil, + jfrogProjectKey: "", includeVulnerabilities: false, - includeLicenses: false, + includeLicenses: false, }, { - name: "Vulnerabilities", - httpCloneUrl: "", - watches: nil, - jfrogProjectKey: "", + name: "Vulnerabilities", + httpCloneUrl: "", + watches: nil, + jfrogProjectKey: "", includeVulnerabilities: true, - includeLicenses: true, + includeLicenses: true, }, } for _, testCase := range testCases { diff --git a/utils/utils.go b/utils/utils.go index a68f17231..8ab400997 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -60,7 +60,6 @@ const ( // type ViolationContext string - var ( TrueVal = true FrogbotVersion = "0.0.0" From 552b5c7fb2dcf16527579c6a4c2681c90d841f77 Mon Sep 17 00:00:00 2001 From: attiasas Date: Sun, 22 Dec 2024 14:10:59 +0300 Subject: [PATCH 26/34] use results context --- .../summary/summary_both_simplified.md | 6 +- .../summary/summary_both_standard.md | 6 +- .../summary/summary_violation_simplified.md | 6 +- .../summary/summary_violation_standard.md | 6 +- utils/outputwriter/outputcontent_test.go | 5 +- utils/scandetails.go | 5 -- utils/scandetails_test.go | 66 +------------------ 7 files changed, 17 insertions(+), 83 deletions(-) diff --git a/testdata/messages/summarycomment/summary/summary_both_simplified.md b/testdata/messages/summarycomment/summary/summary_both_simplified.md index 0f8be7f67..c7c9bf2c2 100644 --- a/testdata/messages/summarycomment/summary/summary_both_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_both_simplified.md @@ -4,12 +4,12 @@ ## 📗 Scan Summary --- -- Frogbot scanned for violations and vulnerabilities and found 9 issues +- Frogbot scanned for violations and vulnerabilities and found 13 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | -| **Software Composition Analysis** | ✅ Done | 6 Issues Found: ❗️ 1 Critical, 🔴 2 High, 🟠 1 Medium, 🟡 1 Low, ⚪️ 1 Unknown | +| **Software Composition Analysis** | ✅ Done | 9 Issues Found: ❗️ 2 Critical, 🔴 3 High, 🟠 2 Medium, 🟡 1 Low, ⚪️ 1 Unknown | | **Contextual Analysis** | ✅ Done | - | -| **Static Application Security Testing (SAST)** | ✅ Done | 3 Issues Found: 🔴 2 High, 🟡 1 Low | +| **Static Application Security Testing (SAST)** | ✅ Done | 4 Issues Found: 🔴 3 High, 🟡 1 Low | | **Secrets** | ✅ Done | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_both_standard.md b/testdata/messages/summarycomment/summary/summary_both_standard.md index 48c8ae3b6..30f1b29a4 100644 --- a/testdata/messages/summarycomment/summary/summary_both_standard.md +++ b/testdata/messages/summarycomment/summary/summary_both_standard.md @@ -1,11 +1,11 @@ ## 📗 Scan Summary -- Frogbot scanned for violations and vulnerabilities and found 9 issues +- Frogbot scanned for violations and vulnerabilities and found 13 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | -| **Software Composition Analysis** | ✅ Done |
6 Issues Found 1 Critical
2 High
1 Medium
1 Low
1 Unknown
| +| **Software Composition Analysis** | ✅ Done |
9 Issues Found 2 Critical
3 High
2 Medium
1 Low
1 Unknown
| | **Contextual Analysis** | ✅ Done | - | -| **Static Application Security Testing (SAST)** | ✅ Done |
3 Issues Found 2 High
1 Low
| +| **Static Application Security Testing (SAST)** | ✅ Done |
4 Issues Found 3 High
1 Low
| | **Secrets** | ✅ Done | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_violation_simplified.md b/testdata/messages/summarycomment/summary/summary_violation_simplified.md index 52c74bae9..58185dd84 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_violation_simplified.md @@ -4,12 +4,12 @@ ## 📗 Scan Summary --- -- Frogbot scanned for violations and found 9 issues +- Frogbot scanned for violations and found 4 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | -| **Software Composition Analysis** | ✅ Done | 6 Issues Found: ❗️ 1 Critical, 🔴 2 High, 🟠 1 Medium, 🟡 1 Low, ⚪️ 1 Unknown | +| **Software Composition Analysis** | ✅ Done | 3 Issues Found: ❗️ 1 Critical, 🔴 1 High, 🟠 1 Medium | | **Contextual Analysis** | ✅ Done | - | -| **Static Application Security Testing (SAST)** | ✅ Done | 3 Issues Found: 🔴 2 High, 🟡 1 Low | +| **Static Application Security Testing (SAST)** | ✅ Done | 1 Issues Found: 🔴 1 High | | **Secrets** | ✅ Done | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_violation_standard.md b/testdata/messages/summarycomment/summary/summary_violation_standard.md index 6f005e487..245da163e 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_standard.md +++ b/testdata/messages/summarycomment/summary/summary_violation_standard.md @@ -1,11 +1,11 @@ ## 📗 Scan Summary -- Frogbot scanned for violations and found 9 issues +- Frogbot scanned for violations and found 4 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | -| **Software Composition Analysis** | ✅ Done |
6 Issues Found 1 Critical
2 High
1 Medium
1 Low
1 Unknown
| +| **Software Composition Analysis** | ✅ Done |
3 Issues Found 1 Critical
1 High
1 Medium
| | **Contextual Analysis** | ✅ Done | - | -| **Static Application Security Testing (SAST)** | ✅ Done |
3 Issues Found 2 High
1 Low
| +| **Static Application Security Testing (SAST)** | ✅ Done |
1 Issues Found 1 High
| | **Secrets** | ✅ Done | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index 51a0b1f4c..19c64be1f 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -234,9 +234,9 @@ func TestScanSummaryContent(t *testing.T) { testCases := []struct { name string - context results.ResultContext includeSecrets bool scanStatus formats.ScanStatus + context results.ResultContext issues issues.ScansIssuesCollection cases []OutputTestCase }{ @@ -261,7 +261,7 @@ func TestScanSummaryContent(t *testing.T) { name: "Vulnerabilities", issues: testIssues, scanStatus: testScanStatus, - context: results.ResultContext{GitRepoHttpsCloneUrl: "url", IncludeVulnerabilities: true}, + context: results.ResultContext{IncludeVulnerabilities: true}, cases: []OutputTestCase{ { name: "Standard output", @@ -338,6 +338,7 @@ func TestScanSummaryContent(t *testing.T) { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) tc.issues.ScanStatus = tc.scanStatus + tc.issues.ResultContext = tc.context output := ScanSummaryContent(tc.issues, tc.context, tc.includeSecrets, test.writer) assert.Equal(t, expectedOutput, output) }) diff --git a/utils/scandetails.go b/utils/scandetails.go index 9d673331a..631464abd 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -70,11 +70,6 @@ func (sc *ScanDetails) SetResultsContext(httpCloneUrl string, watches []string, return sc } -// func (sc *ScanDetails) SetXrayGraphScanParams(httpCloneUrl string, watches []string, jfrogProjectKey string, includeVulnerabilities, includeLicenses bool) *ScanDetails { -// sc.XrayGraphScanParams = createXrayScanParams(httpCloneUrl, watches, jfrogProjectKey, includeVulnerabilities, includeLicenses) -// return sc -// } - func (sc *ScanDetails) SetFixableOnly(fixable bool) *ScanDetails { sc.fixableOnly = fixable return sc diff --git a/utils/scandetails_test.go b/utils/scandetails_test.go index 5617350ce..36f486e8f 100644 --- a/utils/scandetails_test.go +++ b/utils/scandetails_test.go @@ -1,73 +1,11 @@ package utils import ( - "github.com/stretchr/testify/assert" "path/filepath" "testing" -) -func TestCreateResultsContext(t *testing.T) { - testCases := []struct { - name string - httpCloneUrl string - watches []string - jfrogProjectKey string - includeVulnerabilities bool - includeLicenses bool - }{ - { - name: "Violations and Vulnerabilities", - httpCloneUrl: "http://localhost:8080/my-user/my-project.git", - watches: []string{"watch-1", "watch-2"}, - jfrogProjectKey: "project", - includeVulnerabilities: true, - includeLicenses: true, - }, - { - name: "Violations - Project key", - httpCloneUrl: "", - watches: nil, - jfrogProjectKey: "project", - includeVulnerabilities: false, - includeLicenses: true, - }, - { - name: "Violations - Watches", - httpCloneUrl: "", - watches: []string{"watch-1", "watch-2"}, - jfrogProjectKey: "", - includeVulnerabilities: false, - includeLicenses: false, - }, - { - name: "Violations - GitInfoContext", - httpCloneUrl: "http://localhost:8080/my-user/my-project.git", - watches: nil, - jfrogProjectKey: "", - includeVulnerabilities: false, - includeLicenses: false, - }, - { - name: "Vulnerabilities", - httpCloneUrl: "", - watches: nil, - jfrogProjectKey: "", - includeVulnerabilities: true, - includeLicenses: true, - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - scanDetails := &ScanDetails{} - scanDetails.SetResultsContext(testCase.httpCloneUrl, testCase.watches, testCase.jfrogProjectKey, testCase.includeVulnerabilities, testCase.includeLicenses) - assert.Equal(t, testCase.httpCloneUrl, scanDetails.XscGitInfoContext.GitRepoHttpsCloneUrl) - assert.Equal(t, testCase.watches, scanDetails.Watches) - assert.Equal(t, testCase.jfrogProjectKey, scanDetails.ProjectKey) - assert.Equal(t, testCase.includeVulnerabilities, scanDetails.IncludeVulnerabilities) - assert.Equal(t, testCase.includeLicenses, scanDetails.IncludeLicenses) - }) - } -} + "github.com/stretchr/testify/assert" +) func TestGetFullPathWorkingDirs(t *testing.T) { sampleProject := Project{ From bfa79550285d5c7d9acab5fe91ed439f3050e1d7 Mon Sep 17 00:00:00 2001 From: attiasas Date: Mon, 23 Dec 2024 11:01:50 +0300 Subject: [PATCH 27/34] fix issues where versions are not set in details --- scanpullrequest/scanpullrequest.go | 3 +-- scanrepository/scanrepository.go | 3 +-- utils/scandetails.go | 6 ++++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 026795723..20381d10a 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -134,6 +134,7 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) repoConfig.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP scanDetails := utils.NewScanDetails(client, &repoConfig.Server, &repoConfig.Git). + SetJfrogVersions(repoConfig.XrayVersion, repoConfig.XscVersion). SetResultsContext(repositoryInfo.CloneInfo.HTTP, repoConfig.Watches, repoConfig.JFrogProjectKey, repoConfig.IncludeVulnerabilities, len(repoConfig.AllowedLicenses) > 0). SetFixableOnly(repoConfig.FixableOnly). SetFailOnInstallationErrors(*repoConfig.FailOnSecurityIssues). @@ -144,8 +145,6 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) if scanDetails, err = scanDetails.SetMinSeverity(repoConfig.MinSeverity); err != nil { return } - scanDetails.XrayVersion = repoConfig.XrayVersion - scanDetails.XscVersion = repoConfig.XscVersion scanDetails.MultiScanId, scanDetails.StartTime = xsc.SendNewScanEvent( scanDetails.XrayVersion, diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index 310dad463..9d19aefbc 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -124,6 +124,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixBranch(repository *utils.Repository) (er func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Repository, client vcsclient.VcsClient) (err error) { // Set the scan details cfp.scanDetails = utils.NewScanDetails(client, &repository.Server, &repository.Git). + SetJfrogVersions(cfp.XrayVersion, cfp.XscVersion). SetFailOnInstallationErrors(*repository.FailOnSecurityIssues). SetFixableOnly(repository.FixableOnly). SetConfigProfile(repository.ConfigProfile). @@ -136,8 +137,6 @@ func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Reposito return } cfp.scanDetails.SetResultsContext(repositoryInfo.CloneInfo.HTTP, repository.Watches, repository.JFrogProjectKey, repository.IncludeVulnerabilities, len(repository.AllowedLicenses) > 0) - cfp.scanDetails.XrayVersion = cfp.XrayVersion - cfp.scanDetails.XscVersion = cfp.XscVersion if cfp.scanDetails, err = cfp.scanDetails.SetMinSeverity(repository.MinSeverity); err != nil { return diff --git a/utils/scandetails.go b/utils/scandetails.go index 631464abd..2c324c71b 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -50,6 +50,12 @@ func NewScanDetails(client vcsclient.VcsClient, server *config.ServerDetails, gi return &ScanDetails{client: client, ServerDetails: server, Git: git} } +func (sc *ScanDetails) SetJfrogVersions(xrayVersion, xscVersion string) *ScanDetails { + sc.XrayVersion = xrayVersion + sc.XscVersion = xscVersion + return sc +} + func (sc *ScanDetails) SetDisableJas(disable bool) *ScanDetails { sc.disableJas = disable return sc From 9b3ecebd370cdc11fb8d1dcb2883c6b967f5d7da Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 1 Jan 2025 13:21:13 +0300 Subject: [PATCH 28/34] Update deps --- go.mod | 16 +++++++--------- go.sum | 24 ++++++++++++------------ utils/scandetails.go | 2 +- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index a4595afd6..63ffdd5aa 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,9 @@ require ( github.com/jfrog/build-info-go v1.10.7 github.com/jfrog/froggit-go v1.16.2 github.com/jfrog/gofrog v1.7.6 - github.com/jfrog/jfrog-cli-core/v2 v2.57.2 + github.com/jfrog/jfrog-cli-core/v2 v2.57.5 github.com/jfrog/jfrog-cli-security v1.13.6 - github.com/jfrog/jfrog-client-go v1.48.4 + github.com/jfrog/jfrog-client-go v1.48.6 github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/owenrumney/go-sarif/v2 v2.3.1 github.com/stretchr/testify v1.10.0 @@ -56,7 +56,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jedib0t/go-pretty/v6 v6.6.3 // indirect + github.com/jedib0t/go-pretty/v6 v6.6.5 // indirect github.com/jfrog/archiver/v3 v3.6.1 // indirect github.com/jfrog/jfrog-apps-config v1.0.1 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect @@ -64,7 +64,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/ktrysmt/go-bitbucket v0.9.80 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/magiconair/properties v1.8.9 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -106,7 +106,7 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect @@ -118,8 +118,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -// eranturgeman:jas-violations-support -replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241222142559-ad9c8da73af7 +replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security v1.13.9-0.20241231184235-fd5e5f316c30 // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc @@ -127,8 +126,7 @@ replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev -// attiasas:add_repo_context_scan_graph -replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241222093208-082251a38043 +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1 // replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a diff --git a/go.sum b/go.sum index 55ee1c3cd..1e2d56e97 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/attiasas/jfrog-client-go v0.0.0-20241222093208-082251a38043 h1:2JSZ3ol8g3QkxkAc9vOPL9sDPCXPZP6lV9Ot1RfnsgU= -github.com/attiasas/jfrog-client-go v0.0.0-20241222093208-082251a38043/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -57,8 +55,6 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241222142559-ad9c8da73af7 h1:PYC9acGrSt+0J0A/SsoDZjbJxPHZsyqzHa3TmXWS7yk= -github.com/eranturgeman/jfrog-cli-security v0.0.0-20241222142559-ad9c8da73af7/go.mod h1:y1uyCH4S0KhNaR0F3uAUUVuKbfRt11AAOAe1s9B43LA= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= @@ -121,8 +117,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.6.3 h1:nGqgS0tgIO1Hto47HSaaK4ac/I/Bu7usmdD3qvs0WvM= -github.com/jedib0t/go-pretty/v6 v6.6.3/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= +github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3NgSqAo= +github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= github.com/jfrog/build-info-go v1.10.7 h1:10NVHYg0193gJpQft+S4WQfvYMtj5jlwwhJRvkFJtBE= @@ -133,8 +129,12 @@ github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.57.2 h1:2shy1CRWm/8yf6WWfVyAW3AdmryQiI73Tkhfb62vgPE= -github.com/jfrog/jfrog-cli-core/v2 v2.57.2/go.mod h1:sgi0gw96J00Yzx2cKG5xTG/x9XD0YiJbglJOnXUeaD0= +github.com/jfrog/jfrog-cli-core/v2 v2.57.5 h1:guVB/zPPtS8CWpNvAFPCxNvSgVra4TyX8lzs4V4+I/4= +github.com/jfrog/jfrog-cli-core/v2 v2.57.5/go.mod h1:LfKvCRXbvwgE0V6aX3/GabkzCedghXq0Y6lmsEuxr44= +github.com/jfrog/jfrog-cli-security v1.13.9-0.20241231184235-fd5e5f316c30 h1:AzwXYQSwO9c9mvkDIhbxpVZ2yY5npzuV5cOj/q7SXls= +github.com/jfrog/jfrog-cli-security v1.13.9-0.20241231184235-fd5e5f316c30/go.mod h1:cbi7H6G3cTWK7lckwDGT9xoipSneTSEyDL9A/Fy8T7w= +github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1 h1:JQvbTSPDkPNpts1NLHGTKvtG4cMFY1ptBHTNMYFyMhs= +github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= @@ -160,8 +160,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktrysmt/go-bitbucket v0.9.80 h1:S+vZTXKx/VG5yCaX4I3Bmwo8lxWr4ifvuHdTboHTMMc= github.com/ktrysmt/go-bitbucket v0.9.80/go.mod h1:b8ogWEGxQMWoeFnT1ZE4aHIPGindI+9z/zAW/OVFjk0= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -324,8 +324,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= diff --git a/utils/scandetails.go b/utils/scandetails.go index 2c324c71b..b279e0b18 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -72,7 +72,7 @@ func (sc *ScanDetails) SetProject(project *Project) *ScanDetails { } func (sc *ScanDetails) SetResultsContext(httpCloneUrl string, watches []string, jfrogProjectKey string, includeVulnerabilities, includeLicenses bool) *ScanDetails { - sc.ResultContext = audit.CreateResultsContext(sc.ServerDetails, sc.XrayVersion, watches, sc.RepoPath, jfrogProjectKey, httpCloneUrl, includeVulnerabilities, includeLicenses) + sc.ResultContext = audit.CreateAuditResultsContext(sc.ServerDetails, sc.XrayVersion, watches, sc.RepoPath, jfrogProjectKey, httpCloneUrl, includeVulnerabilities, includeLicenses) return sc } From 7e4bbddb04cda8b19b4becb3a71a93bdd50e11fb Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 1 Jan 2025 14:34:16 +0300 Subject: [PATCH 29/34] fix static --- utils/outputwriter/outputcontent.go | 8 -------- utils/scandetails.go | 11 ----------- 2 files changed, 19 deletions(-) diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 8abf776f7..d5f708cb9 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -24,14 +24,6 @@ const ( issuesDetailsSubTitle = "🔖 Details" jfrogResearchDetailsSubTitle = "🔬 JFrog Research Details" - jobErrorTitle = "⚠️ Error Details" - - // Violation Contexts text - NoViolations = "Vulnerabilities" - WatchViolations = "watch violations" - ProjectViolations = "project violations" - GitRepoViolations = "Git repository violations" - policyViolationTitle = "🚥 Policy Violations" securityViolationTitle = "🚨 Security Violations" licenseViolationTitle = "⚖️ License Violations" diff --git a/utils/scandetails.go b/utils/scandetails.go index 861b049ce..af4038c40 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -151,17 +151,6 @@ func (sc *ScanDetails) AllowPartialResults() bool { return sc.allowPartialResults } -func createXrayScanParams(httpCloneUrl string, watches []string, project string, includeVulnerabilities, includeLicenses bool) (params *services.XrayGraphScanParams) { - return &services.XrayGraphScanParams{ - Watches: watches, - ProjectKey: project, - GitRepoHttpsCloneUrl: httpCloneUrl, - IncludeVulnerabilities: includeVulnerabilities, - IncludeLicenses: includeLicenses, - ScanType: services.Dependency, - } -} - func (sc *ScanDetails) RunInstallAndAudit(workDirs ...string) (auditResults *results.SecurityCommandResults) { auditBasicParams := (&utils.AuditBasicParams{}). SetXrayVersion(sc.XrayVersion). From 8c3afc4368fcff9e2a15757b86900a88fe6d0aca Mon Sep 17 00:00:00 2001 From: attiasas Date: Thu, 2 Jan 2025 16:11:39 +0300 Subject: [PATCH 30/34] Fix CR changes --- go.mod | 2 +- go.sum | 4 +- scanpullrequest/scanpullrequest.go | 26 +++++-------- scanrepository/scanrepository.go | 23 +++--------- utils/comment.go | 29 ++++----------- utils/comment_test.go | 10 ++--- utils/issues/issuescollection.go | 21 +++++++---- utils/issues/issuescollection_test.go | 2 +- utils/outputwriter/icons.go | 1 - utils/outputwriter/markdowntable.go | 16 ++++---- utils/outputwriter/outputcontent.go | 38 +++++-------------- utils/params.go | 53 +++++++++++++++------------ utils/scandetails.go | 5 --- utils/utils.go | 10 ----- 14 files changed, 94 insertions(+), 146 deletions(-) diff --git a/go.mod b/go.mod index 63ffdd5aa..b0777992d 100644 --- a/go.mod +++ b/go.mod @@ -118,7 +118,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security v1.13.9-0.20241231184235-fd5e5f316c30 +replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security v1.13.9-0.20250102105538-33f766fefd83 // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc diff --git a/go.sum b/go.sum index 1e2d56e97..966021521 100644 --- a/go.sum +++ b/go.sum @@ -131,8 +131,8 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-core/v2 v2.57.5 h1:guVB/zPPtS8CWpNvAFPCxNvSgVra4TyX8lzs4V4+I/4= github.com/jfrog/jfrog-cli-core/v2 v2.57.5/go.mod h1:LfKvCRXbvwgE0V6aX3/GabkzCedghXq0Y6lmsEuxr44= -github.com/jfrog/jfrog-cli-security v1.13.9-0.20241231184235-fd5e5f316c30 h1:AzwXYQSwO9c9mvkDIhbxpVZ2yY5npzuV5cOj/q7SXls= -github.com/jfrog/jfrog-cli-security v1.13.9-0.20241231184235-fd5e5f316c30/go.mod h1:cbi7H6G3cTWK7lckwDGT9xoipSneTSEyDL9A/Fy8T7w= +github.com/jfrog/jfrog-cli-security v1.13.9-0.20250102105538-33f766fefd83 h1:DKHYPQBsiUntE5hyQucL/q0bu0PRAcXdWEd933ZN+JA= +github.com/jfrog/jfrog-cli-security v1.13.9-0.20250102105538-33f766fefd83/go.mod h1:lwaXWYJirqBhNmQ148xpM2h4s9xGLaoG50T2/Ax2jM0= github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1 h1:JQvbTSPDkPNpts1NLHGTKvtG4cMFY1ptBHTNMYFyMhs= github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 20381d10a..dc17b5efc 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -127,15 +127,14 @@ func toFailTaskStatus(repo *utils.Repository, issues *issues.ScansIssuesCollecti // Downloads Pull Requests branches code and audits them func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) (issuesCollection *issues.ScansIssuesCollection, err error) { - repositoryInfo, err := client.GetRepositoryInfo(context.Background(), repoConfig.RepoOwner, repoConfig.RepoName) + repositoryCloneUrl, err := repoConfig.GetRepositoryHttpsCloneUrl(client) if err != nil { return } - repoConfig.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP scanDetails := utils.NewScanDetails(client, &repoConfig.Server, &repoConfig.Git). SetJfrogVersions(repoConfig.XrayVersion, repoConfig.XscVersion). - SetResultsContext(repositoryInfo.CloneInfo.HTTP, repoConfig.Watches, repoConfig.JFrogProjectKey, repoConfig.IncludeVulnerabilities, len(repoConfig.AllowedLicenses) > 0). + SetResultsContext(repositoryCloneUrl, repoConfig.Watches, repoConfig.JFrogProjectKey, repoConfig.IncludeVulnerabilities, len(repoConfig.AllowedLicenses) > 0). SetFixableOnly(repoConfig.FixableOnly). SetFailOnInstallationErrors(*repoConfig.FailOnSecurityIssues). SetConfigProfile(repoConfig.ConfigProfile). @@ -155,7 +154,7 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) defer func() { if issuesCollection != nil { - xsc.SendScanEndedEvent(scanDetails.XrayVersion, scanDetails.XscVersion, scanDetails.ServerDetails, scanDetails.MultiScanId, scanDetails.StartTime, issuesCollection.GetTotalIssues(true), err) + xsc.SendScanEndedEvent(scanDetails.XrayVersion, scanDetails.XscVersion, scanDetails.ServerDetails, scanDetails.MultiScanId, scanDetails.StartTime, issuesCollection.GetAllIssuesCount(true), err) } }() @@ -252,24 +251,19 @@ func prepareTargetForScan(gitDetails utils.Git, scanDetails *utils.ScanDetails) return } log.Debug("Using most common ancestor commit as target branch commit") + // Get common parent commit between source and target and use it (checkout) to the target branch commit - if e := tryCheckoutToMostCommonAncestor(scanDetails, gitDetails.PullRequestDetails.Source.Name, target.Name, targetBranchWd, gitDetails.RepositoryCloneUrl); e != nil { + repoCloneUrl, err := scanDetails.GetRepositoryHttpsCloneUrl(scanDetails.Client()) + if err != nil { + return + } + if e := tryCheckoutToMostCommonAncestor(scanDetails, gitDetails.PullRequestDetails.Source.Name, target.Name, targetBranchWd, repoCloneUrl); e != nil { log.Warn(fmt.Sprintf("Failed to get best common ancestor commit between source branch: %s and target branch: %s, defaulting to target branch commit. Error: %s", gitDetails.PullRequestDetails.Source.Name, target.Name, e.Error())) } return } func tryCheckoutToMostCommonAncestor(scanDetails *utils.ScanDetails, baseBranch, headBranch, targetBranchWd, cloneRepoUrl string) (err error) { - if cloneRepoUrl != "" { - scanDetails.Git.RepositoryCloneUrl = cloneRepoUrl - } else { - var repositoryInfo vcsclient.RepositoryInfo - repositoryInfo, err = scanDetails.Client().GetRepositoryInfo(context.Background(), scanDetails.RepoOwner, scanDetails.RepoName) - if err != nil { - return - } - scanDetails.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP - } // Change working directory to the temp target branch directory cwd, err := os.Getwd() if err != nil { @@ -282,7 +276,7 @@ func tryCheckoutToMostCommonAncestor(scanDetails *utils.ScanDetails, baseBranch, err = errors.Join(err, os.Chdir(cwd)) }() // Create a new git manager and fetch - gitManager, err := utils.NewGitManager().SetAuth(scanDetails.Username, scanDetails.Token).SetRemoteGitUrl(scanDetails.Git.RepositoryCloneUrl) + gitManager, err := utils.NewGitManager().SetAuth(scanDetails.Username, scanDetails.Token).SetRemoteGitUrl(cloneRepoUrl) if err != nil { return } diff --git a/scanrepository/scanrepository.go b/scanrepository/scanrepository.go index 9d19aefbc..8c339109f 100644 --- a/scanrepository/scanrepository.go +++ b/scanrepository/scanrepository.go @@ -122,9 +122,14 @@ func (cfp *ScanRepositoryCmd) scanAndFixBranch(repository *utils.Repository) (er } func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Repository, client vcsclient.VcsClient) (err error) { + repositoryCloneUrl, err := repository.Git.GetRepositoryHttpsCloneUrl(client) + if err != nil { + return + } // Set the scan details cfp.scanDetails = utils.NewScanDetails(client, &repository.Server, &repository.Git). SetJfrogVersions(cfp.XrayVersion, cfp.XscVersion). + SetResultsContext(repositoryCloneUrl, repository.Watches, repository.JFrogProjectKey, repository.IncludeVulnerabilities, len(repository.AllowedLicenses) > 0). SetFailOnInstallationErrors(*repository.FailOnSecurityIssues). SetFixableOnly(repository.FixableOnly). SetConfigProfile(repository.ConfigProfile). @@ -132,25 +137,9 @@ func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Reposito SetAllowPartialResults(repository.AllowPartialResults). SetDisableJas(repository.DisableJas) - repositoryInfo, err := client.GetRepositoryInfo(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName) - if err != nil { - return - } - cfp.scanDetails.SetResultsContext(repositoryInfo.CloneInfo.HTTP, repository.Watches, repository.JFrogProjectKey, repository.IncludeVulnerabilities, len(repository.AllowedLicenses) > 0) - if cfp.scanDetails, err = cfp.scanDetails.SetMinSeverity(repository.MinSeverity); err != nil { return } - if repository.Git.RepositoryCloneUrl != "" { - cfp.scanDetails.Git.RepositoryCloneUrl = repository.Git.RepositoryCloneUrl - } else { - var repositoryInfo vcsclient.RepositoryInfo - repositoryInfo, err = client.GetRepositoryInfo(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName) - if err != nil { - return - } - cfp.scanDetails.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP - } // Set the flag for aggregating fixes to generate a unified pull request for fixing vulnerabilities cfp.aggregateFixes = repository.Git.AggregateFixes @@ -161,7 +150,7 @@ func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Reposito cfp.gitManager, err = utils.NewGitManager(). SetAuth(cfp.scanDetails.Username, cfp.scanDetails.Token). SetDryRun(cfp.dryRun, cfp.dryRunRepoPath). - SetRemoteGitUrl(cfp.scanDetails.Git.RepositoryCloneUrl) + SetRemoteGitUrl(repositoryCloneUrl) if err != nil { return } diff --git a/utils/comment.go b/utils/comment.go index 9626cff54..f72bb5dda 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -32,21 +32,6 @@ const ( commentRemovalErrorMsg = "An error occurred while attempting to remove older Frogbot pull request comments:" ) -// // In Scan PR, if there is an error, a comment will be added to the PR with the error message. -// func HandlePullRequestErrorComment(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int, scanError error) (err error) { -// if issues == nil { -// log.Debug("Can't generate error comment without issues collection") -// return -// } -// writer := repo.OutputWriter -// for _, comment := range outputwriter.GetFrogbotErrorCommentContent([]string{outputwriter.ScanSummaryContent(*issues, getResultsContextText(repo.ViolationContext), repo.PullRequestSecretComments, writer)}, scanError, writer) { -// if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, comment, pullRequestID); err != nil { -// return errors.New("couldn't add pull request comment: " + err.Error()) -// } -// } -// return -// } - // In Scan PR, if there are no issues, comments will be added to the PR with a message that there are no issues. func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int) (err error) { if !repo.Params.AvoidPreviousPrCommentsDeletion { @@ -243,14 +228,16 @@ func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection return } -type similarIssues struct { +type jasCommentIssues struct { + // The location of the issue that the comment will be added to. formats.Location + // Similar issues at the same location that will be shown in the same comment. issues []formats.SourceCodeRow } -// For JAS violations we can have similar issues at the same location from different watches, we need to group similar issues to add them to the same comment. -func groupSimilarJasIssues(issues []formats.SourceCodeRow) (groupedIssues []similarIssues) { - idToIssues := make(map[string]similarIssues) +// For JAS violations we can have similar issues at the same location, we need to group similar issues to add them to the same comment based on `getSourceCodeRowId`. +func groupSimilarJasIssues(issues []formats.SourceCodeRow) (groupedIssues []jasCommentIssues) { + idToIssues := make(map[string]jasCommentIssues) for _, issue := range issues { id := getSourceCodeRowId(issue) if similarIssue, ok := idToIssues[id]; ok { @@ -258,7 +245,7 @@ func groupSimilarJasIssues(issues []formats.SourceCodeRow) (groupedIssues []simi idToIssues[id] = similarIssue continue } - idToIssues[id] = similarIssues{ + idToIssues[id] = jasCommentIssues{ Location: issue.Location, issues: []formats.SourceCodeRow{issue}, } @@ -269,7 +256,7 @@ func groupSimilarJasIssues(issues []formats.SourceCodeRow) (groupedIssues []simi return } -// We show different comments for each location and rule ID. (we group similar issues/violations to the same comment) +// Similar comment should have the same location and rule-id. func getSourceCodeRowId(issue formats.SourceCodeRow) string { return issue.RuleId + issue.Location.ToString() } diff --git a/utils/comment_test.go b/utils/comment_test.go index c0f6a0fe0..9c7fb29f7 100644 --- a/utils/comment_test.go +++ b/utils/comment_test.go @@ -53,7 +53,7 @@ func TestGroupSimilarJasIssues(t *testing.T) { testCases := []struct { name string issues []formats.SourceCodeRow - groupedIssues []similarIssues + groupedIssues []jasCommentIssues }{ { name: "No issues", @@ -68,7 +68,7 @@ func TestGroupSimilarJasIssues(t *testing.T) { ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, }, }, - groupedIssues: []similarIssues{ + groupedIssues: []jasCommentIssues{ { formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, []formats.SourceCodeRow{ @@ -98,7 +98,7 @@ func TestGroupSimilarJasIssues(t *testing.T) { ScannerInfo: formats.ScannerInfo{RuleId: "rule2"}, }, }, - groupedIssues: []similarIssues{ + groupedIssues: []jasCommentIssues{ { formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, []formats.SourceCodeRow{ @@ -124,7 +124,7 @@ func TestGroupSimilarJasIssues(t *testing.T) { }, }, { - name: "Multiple issues - similar issues", + name: "Multiple issues - with similar issues", issues: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{Severity: "High"}, @@ -151,7 +151,7 @@ func TestGroupSimilarJasIssues(t *testing.T) { ScannerInfo: formats.ScannerInfo{RuleId: "rule1"}, }, }, - groupedIssues: []similarIssues{ + groupedIssues: []jasCommentIssues{ { formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}, []formats.SourceCodeRow{ diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index dadb0c32b..84ece8b2e 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -131,11 +131,11 @@ func (ic *ScansIssuesCollection) HasErrors() bool { return false } -func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubScanType, vulnerabilities, violation bool) map[severityutils.Severity]int { +func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubScanType, vulnerabilities, isViolation bool) map[severityutils.Severity]int { scanDetails := map[severityutils.Severity]int{} if scanType == utils.ScaScan { // Count Sca issues only if requested - if violation { + if isViolation { for _, violation := range ic.ScaViolations { scanDetails[severityutils.GetSeverity(violation.Severity)]++ } @@ -155,7 +155,7 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc switch scanType { case utils.IacScan: // Count Iac issues only if requested - if violation { + if isViolation { jasViolations = ic.IacViolations } if vulnerabilities { @@ -163,7 +163,7 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc } case utils.SecretsScan: // Count Secrets issues only if requested - if violation { + if isViolation { jasViolations = ic.SecretsViolations } if vulnerabilities { @@ -171,7 +171,7 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc } case utils.SastScan: // Count Sast issues only if requested - if violation { + if isViolation { jasViolations = ic.SastViolations } if vulnerabilities { @@ -208,13 +208,18 @@ func (ic *ScansIssuesCollection) SastIssuesExists() bool { return len(ic.SastVulnerabilities) > 0 || len(ic.SastViolations) > 0 } -func (ic *ScansIssuesCollection) GetTotalIssues(includeSecrets bool) int { +func (ic *ScansIssuesCollection) GetAllIssuesCount(includeSecrets bool) int { return ic.GetTotalVulnerabilities(includeSecrets) + ic.GetTotalViolations(includeSecrets) } type ApplicableEvidences struct { - Evidence formats.Evidence - Severity, ScannerDescription, IssueId, CveSummary, ImpactedDependency, Remediation string + Evidence formats.Evidence + Severity string + ScannerDescription string + IssueId string + CveSummary string + ImpactedDependency string + Remediation string } func toApplicableEvidences(issue formats.VulnerabilityOrViolationRow, cve formats.CveRow, evidence formats.Evidence) ApplicableEvidences { diff --git a/utils/issues/issuescollection_test.go b/utils/issues/issuescollection_test.go index 3ae68d714..86cdfe2db 100644 --- a/utils/issues/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -169,7 +169,7 @@ func TestCountIssuesCollectionFindings(t *testing.T) { issuesCollection := getTestData() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - findingsAmount := issuesCollection.GetTotalIssues(tc.includeSecrets) + findingsAmount := issuesCollection.GetAllIssuesCount(tc.includeSecrets) assert.Equal(t, tc.expectedFindings, findingsAmount) }) } diff --git a/utils/outputwriter/icons.go b/utils/outputwriter/icons.go index fcc4328cf..49c341a0c 100644 --- a/utils/outputwriter/icons.go +++ b/utils/outputwriter/icons.go @@ -10,7 +10,6 @@ type IconName string const ( baseResourceUrl = "https://raw.githubusercontent.com/jfrog/frogbot/master/resources/" - // baseResourceUrl = "https://raw.githubusercontent.com/attiasas/frogbot/jas_violations/resources/" NoVulnerabilityPrBannerSource ImageSource = "v2/noVulnerabilityBannerPR.png" NoVulnerabilityMrBannerSource ImageSource = "v2/noVulnerabilityBannerMR.png" diff --git a/utils/outputwriter/markdowntable.go b/utils/outputwriter/markdowntable.go index da6c81e6b..c661def2c 100644 --- a/utils/outputwriter/markdowntable.go +++ b/utils/outputwriter/markdowntable.go @@ -143,36 +143,38 @@ func (t *MarkdownTableBuilder) Build() string { var tableBuilder strings.Builder // Calculate Hidden columns for c := range t.columns { - // Reset shouldHideColumn flag + // Reset shouldHideColumn flag to the defined value in the column + // If the column OmitEmpty flag is set, the column will be hidden if all the values in the column are empty t.columns[c].shouldHideColumn = t.columns[c].OmitEmpty } for _, row := range t.rows { for c, cell := range row { // In table, empty cell = cell with no values = cell with one empty value + // So we want don't want to hide the column if at least one cell has a value in it t.columns[c].shouldHideColumn = t.columns[c].shouldHideColumn && (len(cell) == 0 || (len(cell) == 1 && cell[0] == "")) } } // Header - actualColumnCount := 0 + isFirstCol := true for _, column := range t.columns { if column.shouldHideColumn { continue } - if actualColumnCount == 0 { + if isFirstCol { tableBuilder.WriteString(fmt.Sprintf(firstCellPlaceholder, column.Name)) } else { tableBuilder.WriteString(fmt.Sprintf(cellPlaceholder, column.Name)) } - actualColumnCount++ + isFirstCol = false } tableBuilder.WriteString("\n") // Separator - actualColumnCount = 0 + isFirstCol = true for _, column := range t.columns { if column.shouldHideColumn { continue } - if actualColumnCount == 0 { + if isFirstCol { columnSeparator := defaultFirstColumnSeparator if column.Centered { columnSeparator = centeredFirstColumnSeparator @@ -185,7 +187,7 @@ func (t *MarkdownTableBuilder) Build() string { } tableBuilder.WriteString(columnSeparator) } - actualColumnCount++ + isFirstCol = false } // Content for _, row := range t.rows { diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index d5f708cb9..51b93a9e9 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -149,32 +149,6 @@ func footer(writer OutputWriter) string { return fmt.Sprintf("%s\n%s", SectionDivider(), writer.MarkInCenter(CommentGeneratedByFrogbot)) } -// func GetFrogbotErrorCommentContent(contentForComments []string, err error, writer OutputWriter) (comments []string) { -// // First decorate with error suffix, then wrap content with the base decorator -// return ConvertContentToComments(contentForComments, writer, getFrogbotErrorSuffixDecorator(writer, err), GetFrogbotCommentBaseDecorator(writer)) -// } - -// Adding markdown suffix to show the error details and next steps -// func getFrogbotErrorSuffixDecorator(writer OutputWriter, err error) CommentDecorator { -// return func(_ int, content string) string { -// var comment strings.Builder -// WriteNewLine(&comment) -// // Error -// WriteContent(&comment, writer.MarkAsTitle("Error:", 4), err.Error()) -// WriteNewLine(&comment) -// // Action steps -// WriteContent(&comment, -// writer.MarkAsTitle("Next Steps:", 4), -// "1. Please try to rerun the scan.", -// fmt.Sprintf("2. If the issue persists, consider checking the %s for troubleshooting tips.", MarkAsLink("Frogbot documentation", FrogbotDocumentationUrl)), -// fmt.Sprintf("3. If you still need assistance, feel free to reach out to %s.", MarkAsLink("JFrog Support", JfrogSupportUrl)), -// ) -// WriteNewLine(&comment) -// WriteContent(&comment, "Thank you for your understanding!") -// return content + "\n" + writer.MarkAsDetails(jobErrorTitle, 3, comment.String()) -// } -// } - // Summary content func ScanSummaryContent(issues issues.ScansIssuesCollection, context results.ResultContext, includeSecrets bool, writer OutputWriter) string { @@ -409,15 +383,18 @@ func getLicenseViolationsDetailsContent(licenseViolations []formats.LicenseViola } for _, violation := range licenseViolations { if len(licenseViolations) == 1 { + // No need for
tag if there is only one violation, just show the details content = append(content, getScaLicenseViolationDetails(violation, writer)) } else { + // Add wrap the content of each violation in a
tag content = append(content, "\n"+writer.MarkAsDetails( - getComponentIssueIdentifier(violation.LicenseKey, violation.ImpactedDependencyName, violation.ImpactedDependencyVersion, violation.Watch), 4, + getComponentIssueIdentifier(violation.LicenseKey, violation.ImpactedDependencyName, violation.ImpactedDependencyVersion, violation.Watch), + 4, getScaLicenseViolationDetails(violation, writer), )) } } - // Split content if it exceeds the size limit and decorate it with title + // Split content if it exceeds the size limit and decorate each comment with title as prefix return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { contentBuilder := strings.Builder{} WriteContent(&contentBuilder, writer.MarkAsTitle(issuesDetailsSubTitle, 3)) @@ -632,15 +609,17 @@ func getScaSecurityIssueDetailsContent(issues []formats.VulnerabilityOrViolation } for _, issue := range issuesWithDetails { if len(issues) == 1 { + // No need for
tag if there is only one issue, just show the details content = append(content, getScaSecurityIssueDetails(issue, violations, writer)) } else { + // Add wrap the content of each issue in a
tag content = append(content, "\n"+writer.MarkAsDetails( getComponentIssueIdentifier(results.GetIssueIdentifier(issue.Cves, issue.IssueId, ", "), issue.ImpactedDependencyName, issue.ImpactedDependencyVersion, issue.Watch), 4, getScaSecurityIssueDetails(issue, violations, writer), )) } } - // Split content if it exceeds the size limit and decorate it with title + // Split content if it exceeds the size limit and decorate it with title as prefix return ConvertContentToComments(content, writer, func(commentCount int, detailsContent string) string { contentBuilder := strings.Builder{} WriteContent(&contentBuilder, writer.MarkAsTitle(issuesDetailsSubTitle, 3)) @@ -777,6 +756,7 @@ func getJasFullDescription(violations bool, writer OutputWriter, generateRuleTab if len(rulesInfo) == 1 { WriteContent(&contentBuilder, writer.MarkAsDetails("Full description", 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer)), + // TODO: should remove this? codeFlowsReviewContent(scannerCodeFlows, writer), ) break diff --git a/utils/params.go b/utils/params.go index d2f42e3b1..cbc6cfe96 100644 --- a/utils/params.go +++ b/utils/params.go @@ -317,22 +317,34 @@ func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { type Git struct { GitProvider vcsutils.VcsProvider vcsclient.VcsInfo - UseMostCommonAncestorAsTarget bool `yaml:"useMostCommonAncestorAsTarget,omitempty"` - RepoOwner string - RepoName string `yaml:"repoName,omitempty"` - Branches []string `yaml:"branches,omitempty"` - BranchNameTemplate string `yaml:"branchNameTemplate,omitempty"` - CommitMessageTemplate string `yaml:"commitMessageTemplate,omitempty"` - PullRequestTitleTemplate string `yaml:"pullRequestTitleTemplate,omitempty"` - PullRequestCommentTitle string `yaml:"pullRequestCommentTitle,omitempty"` - PullRequestSecretComments bool `yaml:"pullRequestSecretComments,omitempty"` - PullRequestDisableErrorComment bool `yaml:"pullRequestDisableErrorComment,omitempty"` - AvoidExtraMessages bool `yaml:"avoidExtraMessages,omitempty"` - EmailAuthor string `yaml:"emailAuthor,omitempty"` - AggregateFixes bool `yaml:"aggregateFixes,omitempty"` - PullRequestDetails vcsclient.PullRequestInfo - RepositoryCloneUrl string - UseLocalRepository bool + UseMostCommonAncestorAsTarget bool `yaml:"useMostCommonAncestorAsTarget,omitempty"` + RepoOwner string + RepoName string `yaml:"repoName,omitempty"` + Branches []string `yaml:"branches,omitempty"` + BranchNameTemplate string `yaml:"branchNameTemplate,omitempty"` + CommitMessageTemplate string `yaml:"commitMessageTemplate,omitempty"` + PullRequestTitleTemplate string `yaml:"pullRequestTitleTemplate,omitempty"` + PullRequestCommentTitle string `yaml:"pullRequestCommentTitle,omitempty"` + PullRequestSecretComments bool `yaml:"pullRequestSecretComments,omitempty"` + AvoidExtraMessages bool `yaml:"avoidExtraMessages,omitempty"` + EmailAuthor string `yaml:"emailAuthor,omitempty"` + AggregateFixes bool `yaml:"aggregateFixes,omitempty"` + PullRequestDetails vcsclient.PullRequestInfo + RepositoryCloneUrl string + UseLocalRepository bool +} + +func (g *Git) GetRepositoryHttpsCloneUrl(gitClient vcsclient.VcsClient) (string, error) { + if g.RepositoryCloneUrl != "" { + return g.RepositoryCloneUrl, nil + } + // If the repository clone URL is not cached, we fetch it from the VCS provider + repositoryInfo, err := gitClient.GetRepositoryInfo(context.Background(), g.RepoOwner, g.RepoName) + if err != nil { + return "", fmt.Errorf("failed to fetch the repository clone URL. %s", err.Error()) + } + g.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP + return g.RepositoryCloneUrl, nil } func (g *Git) setDefaultsIfNeeded(gitParamsFromEnv *Git, commandName string) (err error) { @@ -821,7 +833,6 @@ func getConfigProfileIfExistsAndValid(xrayVersion, xscVersion string, jfrogServe log.Debug(fmt.Sprintf("Configuration Profile usage is disabled. All configurations will be derived from environment variables and files.\nTo enable a Configuration Profile, please set %s to TRUE", JfrogUseConfigProfileEnv)) return } - // Attempt to get the config profile by profile's name profileName := getTrimmedEnv(JfrogConfigProfileEnv) if profileName != "" { @@ -832,14 +843,10 @@ func getConfigProfileIfExistsAndValid(xrayVersion, xscVersion string, jfrogServe err = verifyConfigProfileValidity(configProfile) return } - // Getting repository's url in order to get repository HTTP url - repositoryInfo, err := gitClient.GetRepositoryInfo(context.Background(), gitParams.RepoOwner, gitParams.RepoName) - if err != nil { - return nil, "", err + if repoCloneUrl, err = gitParams.GetRepositoryHttpsCloneUrl(gitClient); err != nil { + return } - repoCloneUrl = repositoryInfo.CloneInfo.HTTP - // Attempt to get a config profile associated with the repo URL log.Debug(fmt.Sprintf("Configuration profile was requested. Searching profile associated to repository '%s'", jfrogServer.Url)) if configProfile, err = xsc.GetConfigProfileByUrl(xrayVersion, jfrogServer, repoCloneUrl); err != nil || configProfile == nil { diff --git a/utils/scandetails.go b/utils/scandetails.go index af4038c40..0dd75d135 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -3,7 +3,6 @@ package utils import ( "context" "fmt" - // "os" "path/filepath" "time" @@ -11,14 +10,11 @@ import ( "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/jfrog-cli-core/v2/utils/config" - // "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-security/commands/audit" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" - // "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" "github.com/jfrog/jfrog-client-go/utils/log" - "github.com/jfrog/jfrog-client-go/xray/services" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" ) @@ -26,7 +22,6 @@ type ScanDetails struct { *Project *Git - // *services.XrayGraphScanParams *xscservices.XscGitInfoContext *config.ServerDetails client vcsclient.VcsClient diff --git a/utils/utils.go b/utils/utils.go index 774c75cf6..388484533 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -50,16 +50,6 @@ const ( JfrogHomeDirEnv = "JFROG_CLI_HOME_DIR" ) -// ViolationContext is a type for violation context (None,Project,GitRepo) -// const ( -// None ViolationContext = "" // No violation context -// WatchContext ViolationContext = "watch" -// ProjectContext ViolationContext = "project" -// GitRepoContext ViolationContext = "git" -// ) - -// type ViolationContext string - var ( TrueVal = true FrogbotVersion = "0.0.0" From d9acc513626abf42b4bc010b11ccecaca750e3e9 Mon Sep 17 00:00:00 2001 From: attiasas Date: Thu, 2 Jan 2025 16:20:43 +0300 Subject: [PATCH 31/34] more CR changes --- scanpullrequest/scanpullrequest.go | 10 +++++----- utils/comment.go | 9 +++++---- utils/issues/issuescollection.go | 3 --- utils/outputwriter/outputcontent.go | 4 ++-- utils/outputwriter/outputcontent_test.go | 1 - 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index dc17b5efc..875263355 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -93,7 +93,7 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er log.Info("-----------------------------------------------------------") // Audit PR code - issues, err := auditPullRequest(repo, client) + issues, resultContext, err := auditPullRequest(repo, client) if err != nil { return } @@ -108,7 +108,7 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er } // Handle PR comments for scan output - if err = utils.HandlePullRequestCommentsAfterScan(issues, repo, client, int(pullRequestDetails.ID)); err != nil { + if err = utils.HandlePullRequestCommentsAfterScan(issues, resultContext, repo, client, int(pullRequestDetails.ID)); err != nil { return } @@ -126,7 +126,7 @@ func toFailTaskStatus(repo *utils.Repository, issues *issues.ScansIssuesCollecti } // Downloads Pull Requests branches code and audits them -func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) (issuesCollection *issues.ScansIssuesCollection, err error) { +func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) (issuesCollection *issues.ScansIssuesCollection, resultContext results.ResultContext, err error) { repositoryCloneUrl, err := repoConfig.GetRepositoryHttpsCloneUrl(client) if err != nil { return @@ -169,6 +169,7 @@ func auditPullRequest(repoConfig *utils.Repository, client vcsclient.VcsClient) } issuesCollection.Append(projectIssues) } + resultContext = scanDetails.ResultContext return } @@ -304,7 +305,6 @@ func getAllIssues(cmdResults *results.SecurityCommandResults, allowedLicenses [] return nil, err } return &issues.ScansIssuesCollection{ - ResultContext: cmdResults.ResultContext, ScanStatus: simpleJsonResults.Statuses, ScaVulnerabilities: simpleJsonResults.Vulnerabilities, ScaViolations: simpleJsonResults.SecurityViolations, @@ -346,7 +346,7 @@ func getNewlyAddedIssues(targetResults, sourceResults *results.SecurityCommandRe return } // ResultContext is general attribute similar for all results, taking it from the source results - newIssues = &issues.ScansIssuesCollection{ResultContext: sourceResults.ResultContext} + newIssues = &issues.ScansIssuesCollection{} newIssues.ScanStatus = getScanStatus(simpleJsonTarget, simpleJsonSource) // Get the unique sca vulnerabilities and violations between the source and target branches newIssues.ScaVulnerabilities = getUniqueVulnerabilityOrViolationRows(simpleJsonTarget.Vulnerabilities, simpleJsonSource.Vulnerabilities) diff --git a/utils/comment.go b/utils/comment.go index f72bb5dda..6c1563a11 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -11,6 +11,7 @@ import ( "github.com/jfrog/frogbot/v2/utils/outputwriter" "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -33,7 +34,7 @@ const ( ) // In Scan PR, if there are no issues, comments will be added to the PR with a message that there are no issues. -func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, repo *Repository, client vcsclient.VcsClient, pullRequestID int) (err error) { +func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, resultContext results.ResultContext, repo *Repository, client vcsclient.VcsClient, pullRequestID int) (err error) { if !repo.Params.AvoidPreviousPrCommentsDeletion { // The removal of comments may fail for various reasons, // such as concurrent scanning of pull requests and attempts @@ -48,7 +49,7 @@ func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, re // Add summary (SCA, license) scan comment if issues.IssuesExists(repo.PullRequestSecretComments) || repo.AddPrCommentOnSuccess { - for _, comment := range generatePullRequestSummaryComment(*issues, repo.PullRequestSecretComments, repo.OutputWriter) { + for _, comment := range generatePullRequestSummaryComment(*issues, resultContext, repo.PullRequestSecretComments, repo.OutputWriter) { if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, comment, pullRequestID); err != nil { err = errors.New("couldn't add pull request comment: " + err.Error()) return @@ -110,13 +111,13 @@ func GenerateFixPullRequestDetails(vulnerabilities []formats.VulnerabilityOrViol return } -func generatePullRequestSummaryComment(issuesCollection issues.ScansIssuesCollection, includeSecrets bool, writer outputwriter.OutputWriter) []string { +func generatePullRequestSummaryComment(issuesCollection issues.ScansIssuesCollection, resultContext results.ResultContext, includeSecrets bool, writer outputwriter.OutputWriter) []string { if !issuesCollection.IssuesExists(includeSecrets) { // No Issues return outputwriter.GetMainCommentContent([]string{}, false, true, writer) } // Summary - content := []string{outputwriter.ScanSummaryContent(issuesCollection, issuesCollection.ResultContext, includeSecrets, writer)} + content := []string{outputwriter.ScanSummaryContent(issuesCollection, resultContext, includeSecrets, writer)} // Violations if violationsContent := outputwriter.PolicyViolationsContent(issuesCollection, writer); len(violationsContent) > 0 { content = append(content, violationsContent...) diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index 84ece8b2e..e611cc9df 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -11,7 +11,6 @@ import ( // Group issues by scan type type ScansIssuesCollection struct { formats.ScanStatus - results.ResultContext LicensesViolations []formats.LicenseViolationRow @@ -34,8 +33,6 @@ func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { if issues == nil { return } - // Result context should be the same for all collections - ic.ResultContext = issues.ResultContext // Status ic.AppendStatus(issues.ScanStatus) // Sca diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 51b93a9e9..981ea2032 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -157,10 +157,10 @@ func ScanSummaryContent(issues issues.ScansIssuesCollection, context results.Res } var contentBuilder strings.Builder totalIssues := 0 - if issues.HasViolationContext() { + if context.HasViolationContext() { totalIssues += issues.GetTotalViolations(includeSecrets) } - if issues.IncludeVulnerabilities { + if context.IncludeVulnerabilities { totalIssues += issues.GetTotalVulnerabilities(includeSecrets) } // Title diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index 19c64be1f..29222ef0c 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -338,7 +338,6 @@ func TestScanSummaryContent(t *testing.T) { t.Run(tc.name+"_"+test.name, func(t *testing.T) { expectedOutput := GetExpectedTestOutput(t, test) tc.issues.ScanStatus = tc.scanStatus - tc.issues.ResultContext = tc.context output := ScanSummaryContent(tc.issues, tc.context, tc.includeSecrets, test.writer) assert.Equal(t, expectedOutput, output) }) From 1ac0725b79f1e7a94d467bcd5f3e23402fcb730c Mon Sep 17 00:00:00 2001 From: attiasas Date: Thu, 2 Jan 2025 17:32:09 +0300 Subject: [PATCH 32/34] fix tests --- scanpullrequest/scanpullrequest_test.go | 15 +++++++++------ .../test_proj_pip_with_vulnerability.md | 2 +- .../test_proj_with_vulnerability_simplified.md | 2 +- .../test_proj_with_vulnerability_standard.md | 2 +- testdata/scanpullrequest/expected_response.md | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index 7a54c3355..9c7ed2ede 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -445,7 +445,7 @@ func TestGetNewVulnerabilitiesCaseNoNewVulnerabilities(t *testing.T) { func TestGetAllIssues(t *testing.T) { allowedLicenses := []string{"MIT"} - auditResults := &results.SecurityCommandResults{EntitledForJas: true, Targets: []*results.TargetResults{{ + auditResults := &results.SecurityCommandResults{EntitledForJas: true, ResultContext: results.ResultContext{IncludeVulnerabilities: true}, Targets: []*results.TargetResults{{ ScanTarget: results.ScanTarget{Target: "dummy"}, ScaResults: &results.ScaScanResults{ XrayResults: validations.NewMockScaResults(services.ScanResponse{ @@ -497,7 +497,7 @@ func TestGetAllIssues(t *testing.T) { SeverityDetails: formats.SeverityDetails{Severity: "High", SeverityNumValue: 21}, ImpactedDependencyName: "Dep-1", }, - Cves: []formats.CveRow{{Id: "CVE-2022-2122", Applicability: &formats.Applicability{Status: "Applicable", Evidence: []formats.Evidence{{Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, + Cves: []formats.CveRow{{Id: "CVE-2022-2122", Applicability: &formats.Applicability{Status: "Applicable", ScannerDescription: "rule-msg", Evidence: []formats.Evidence{{Reason: "result-msg", Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, }, { Applicable: "Not Applicable", @@ -506,7 +506,7 @@ func TestGetAllIssues(t *testing.T) { SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 2}, ImpactedDependencyName: "Dep-2", }, - Cves: []formats.CveRow{{Id: "CVE-2023-3122", Applicability: &formats.Applicability{Status: "Not Applicable"}}}, + Cves: []formats.CveRow{{Id: "CVE-2023-3122", Applicability: &formats.Applicability{Status: "Not Applicable", ScannerDescription: "rule-msg"}}}, }, }, IacVulnerabilities: []formats.SourceCodeRow{ @@ -516,7 +516,8 @@ func TestGetAllIssues(t *testing.T) { SeverityNumValue: 21, }, ScannerInfo: formats.ScannerInfo{ - RuleId: "rule", + ScannerDescription: "rule-msg", + RuleId: "rule", }, Finding: "Missing auto upgrade was detected", Location: formats.Location{ @@ -536,7 +537,8 @@ func TestGetAllIssues(t *testing.T) { SeverityNumValue: 21, }, ScannerInfo: formats.ScannerInfo{ - RuleId: "rule", + ScannerDescription: "rule-msg", + RuleId: "rule", }, Finding: "Secret", Location: formats.Location{ @@ -556,7 +558,8 @@ func TestGetAllIssues(t *testing.T) { SeverityNumValue: 21, }, ScannerInfo: formats.ScannerInfo{ - RuleId: "rule", + ScannerDescription: "rule-msg", + RuleId: "rule", }, Finding: "XSS Vulnerability", Location: formats.Location{ diff --git a/testdata/messages/integration/test_proj_pip_with_vulnerability.md b/testdata/messages/integration/test_proj_pip_with_vulnerability.md index bbf3d31c8..03c9b00cf 100644 --- a/testdata/messages/integration/test_proj_pip_with_vulnerability.md +++ b/testdata/messages/integration/test_proj_pip_with_vulnerability.md @@ -11,7 +11,7 @@ ## 📗 Scan Summary -- Frogbot scanned for Vulnerabilities and found 1 issues +- Frogbot scanned for vulnerabilities and found 1 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | diff --git a/testdata/messages/integration/test_proj_with_vulnerability_simplified.md b/testdata/messages/integration/test_proj_with_vulnerability_simplified.md index 06aff0274..bbec4b096 100644 --- a/testdata/messages/integration/test_proj_with_vulnerability_simplified.md +++ b/testdata/messages/integration/test_proj_with_vulnerability_simplified.md @@ -10,7 +10,7 @@ ## 📗 Scan Summary --- -- Frogbot scanned for Vulnerabilities and found 1 issues +- Frogbot scanned for vulnerabilities and found 1 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | diff --git a/testdata/messages/integration/test_proj_with_vulnerability_standard.md b/testdata/messages/integration/test_proj_with_vulnerability_standard.md index 64317f7fb..836075a91 100644 --- a/testdata/messages/integration/test_proj_with_vulnerability_standard.md +++ b/testdata/messages/integration/test_proj_with_vulnerability_standard.md @@ -11,7 +11,7 @@ ## 📗 Scan Summary -- Frogbot scanned for Vulnerabilities and found 1 issues +- Frogbot scanned for vulnerabilities and found 1 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | diff --git a/testdata/scanpullrequest/expected_response.md b/testdata/scanpullrequest/expected_response.md index 64317f7fb..836075a91 100644 --- a/testdata/scanpullrequest/expected_response.md +++ b/testdata/scanpullrequest/expected_response.md @@ -11,7 +11,7 @@ ## 📗 Scan Summary -- Frogbot scanned for Vulnerabilities and found 1 issues +- Frogbot scanned for vulnerabilities and found 1 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | From 1c7b517b3f92cc5b2585d166aa62db42c56be0c5 Mon Sep 17 00:00:00 2001 From: attiasas Date: Sun, 5 Jan 2025 14:55:05 +0300 Subject: [PATCH 33/34] fix tests --- go.mod | 3 +- go.sum | 4 +- scanpullrequest/scanpullrequest_test.go | 4 +- .../iac/iac_review_content_simplified.md | 1 - .../iac/iac_review_content_standard.md | 2 +- ...iac_violation_review_content_simplified.md | 1 - .../iac_violation_review_content_standard.md | 2 +- ..._review_content_no_code_flow_simplified.md | 1 - ...st_review_content_no_code_flow_standard.md | 2 +- .../sast/sast_review_content_standard.md | 4 +- ...ast_violation_review_content_simplified.md | 38 ++++++------- .../sast_violation_review_content_standard.md | 22 ++++---- .../secret_review_content_no_ca_simplified.md | 1 - .../secret_review_content_no_ca_standard.md | 2 +- .../secret_review_content_simplified.md | 1 - .../secrets/secret_review_content_standard.md | 2 +- ...ret_violation_review_content_simplified.md | 1 - ...ecret_violation_review_content_standard.md | 2 +- utils/outputwriter/outputcontent.go | 54 ++++++++++++------- 19 files changed, 80 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index b0777992d..6d9c20a9d 100644 --- a/go.mod +++ b/go.mod @@ -118,7 +118,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security v1.13.9-0.20250102105538-33f766fefd83 +//attiasas:simple_json_tech_use +replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-security v0.0.0-20250105112737-1e799179a1d1 // replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc diff --git a/go.sum b/go.sum index 966021521..8cea2a56a 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/attiasas/jfrog-cli-security v0.0.0-20250105112737-1e799179a1d1 h1:/tAdaK+vr0PkIlxnxfl4Pw9k+WDAV9XkG/GquP9Y2Xs= +github.com/attiasas/jfrog-cli-security v0.0.0-20250105112737-1e799179a1d1/go.mod h1:lwaXWYJirqBhNmQ148xpM2h4s9xGLaoG50T2/Ax2jM0= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -131,8 +133,6 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-core/v2 v2.57.5 h1:guVB/zPPtS8CWpNvAFPCxNvSgVra4TyX8lzs4V4+I/4= github.com/jfrog/jfrog-cli-core/v2 v2.57.5/go.mod h1:LfKvCRXbvwgE0V6aX3/GabkzCedghXq0Y6lmsEuxr44= -github.com/jfrog/jfrog-cli-security v1.13.9-0.20250102105538-33f766fefd83 h1:DKHYPQBsiUntE5hyQucL/q0bu0PRAcXdWEd933ZN+JA= -github.com/jfrog/jfrog-cli-security v1.13.9-0.20250102105538-33f766fefd83/go.mod h1:lwaXWYJirqBhNmQ148xpM2h4s9xGLaoG50T2/Ax2jM0= github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1 h1:JQvbTSPDkPNpts1NLHGTKvtG4cMFY1ptBHTNMYFyMhs= github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index 9c7ed2ede..d9982fe01 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -333,7 +333,7 @@ func TestGetNewVulnerabilities(t *testing.T) { SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 13}, ImpactedDependencyName: "component-C", }, - Cves: []formats.CveRow{{Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable", Evidence: []formats.Evidence{{Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, + Cves: []formats.CveRow{{Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable", ScannerDescription: "rule-msg", Evidence: []formats.Evidence{{Reason: "result-msg", Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, Technology: techutils.Yarn, }, { @@ -344,7 +344,7 @@ func TestGetNewVulnerabilities(t *testing.T) { SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 13}, ImpactedDependencyName: "component-D", }, - Cves: []formats.CveRow{{Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable", Evidence: []formats.Evidence{{Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, + Cves: []formats.CveRow{{Id: "CVE-2023-4321", Applicability: &formats.Applicability{Status: "Applicable", ScannerDescription: "rule-msg", Evidence: []formats.Evidence{{Reason: "result-msg", Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, Technology: techutils.Yarn, }, } diff --git a/testdata/messages/reviewcomment/iac/iac_review_content_simplified.md b/testdata/messages/reviewcomment/iac/iac_review_content_simplified.md index 0f02afac6..771f3e4c5 100644 --- a/testdata/messages/reviewcomment/iac/iac_review_content_simplified.md +++ b/testdata/messages/reviewcomment/iac/iac_review_content_simplified.md @@ -22,4 +22,3 @@ --- Scanner Description.... - diff --git a/testdata/messages/reviewcomment/iac/iac_review_content_standard.md b/testdata/messages/reviewcomment/iac/iac_review_content_standard.md index b12b95894..2800ec55d 100644 --- a/testdata/messages/reviewcomment/iac/iac_review_content_standard.md +++ b/testdata/messages/reviewcomment/iac/iac_review_content_standard.md @@ -14,4 +14,4 @@ ### Vulnerability Details Scanner Description.... -
+
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md b/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md index 5e4e2b2dc..bc1b3a215 100644 --- a/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md +++ b/testdata/messages/reviewcomment/iac/iac_violation_review_content_simplified.md @@ -22,4 +22,3 @@ --- Scanner Description.... - diff --git a/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md b/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md index ee2114944..65b56b2cc 100644 --- a/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md +++ b/testdata/messages/reviewcomment/iac/iac_violation_review_content_standard.md @@ -14,4 +14,4 @@ ### Violation Details Scanner Description.... -
+
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_simplified.md b/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_simplified.md index 55583c608..e7c8a9fb4 100644 --- a/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_simplified.md +++ b/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_simplified.md @@ -27,4 +27,3 @@ Scanner Description.... - diff --git a/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_standard.md b/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_standard.md index 6b358b6fb..a02a52a9d 100644 --- a/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_standard.md +++ b/testdata/messages/reviewcomment/sast/sast_review_content_no_code_flow_standard.md @@ -19,4 +19,4 @@ Scanner Description.... -
+
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/sast/sast_review_content_standard.md b/testdata/messages/reviewcomment/sast/sast_review_content_standard.md index ef8888f76..29d579cd3 100644 --- a/testdata/messages/reviewcomment/sast/sast_review_content_standard.md +++ b/testdata/messages/reviewcomment/sast/sast_review_content_standard.md @@ -19,7 +19,7 @@ Scanner Description.... -
+
Code Flows
Vulnerable data flow analysis result @@ -32,4 +32,4 @@ Scanner Description.... ↘️ `a-snippet` (at file line 10) ↘️ `snippet` (at file line 0) -

\ No newline at end of file +


\ No newline at end of file diff --git a/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md b/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md index 8c3aba08a..a51db5c0c 100644 --- a/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md +++ b/testdata/messages/reviewcomment/sast/sast_violation_review_content_simplified.md @@ -12,7 +12,7 @@ --- -### [ Use of Insecure Random ] +### [ Express Not Using Helmet ] --- @@ -24,28 +24,36 @@ --- | | | | --------------------- | :-----------------------------------: | -| **CWE:** | CWE-798, CWE-799 | -| **Rule ID:** | js-insecure-random | +| **Rule ID:** | js-express-without-helmet | Scanner Description.... --- -### Code Flows +### [ Use of Insecure Random ] --- + --- -#### Vulnerable data flow analysis result +### Violation Details --- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-798, CWE-799 | +| **Rule ID:** | js-insecure-random | +Scanner Description.... -↘️ `other-snippet` (at file2 line 1) -↘️ `snippet` (at file line 0) + +--- +### Code Flows + +--- --- @@ -54,25 +62,17 @@ Scanner Description.... --- -↘️ `a-snippet` (at file line 10) +↘️ `other-snippet` (at file2 line 1) ↘️ `snippet` (at file line 0) --- -### [ Express Not Using Helmet ] - ---- - - +#### Vulnerable data flow analysis result --- -### Violation Details ---- -| | | -| --------------------- | :-----------------------------------: | -| **Rule ID:** | js-express-without-helmet | -Scanner Description.... +↘️ `a-snippet` (at file line 10) +↘️ `snippet` (at file line 0) diff --git a/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md b/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md index a44ecd7df..a7336d73c 100644 --- a/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md +++ b/testdata/messages/reviewcomment/sast/sast_violation_review_content_standard.md @@ -11,6 +11,16 @@
+
[ Express Not Using Helmet ] + +### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **Rule ID:** | js-express-without-helmet | + +Scanner Description.... + +
[ Use of Insecure Random ] ### Violation Details @@ -34,14 +44,4 @@ Scanner Description.... ↘️ `a-snippet` (at file line 10) ↘️ `snippet` (at file line 0) -


-
[ Express Not Using Helmet ] - -### Violation Details -| | | -| --------------------- | :-----------------------------------: | -| **Rule ID:** | js-express-without-helmet | - -Scanner Description.... - -
\ No newline at end of file +


\ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md index 70bd2a4f0..bd908fba0 100644 --- a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_simplified.md @@ -27,4 +27,3 @@ Scanner Description.... - diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md index 70498c887..07e267c96 100644 --- a/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_no_ca_standard.md @@ -19,4 +19,4 @@ Scanner Description.... -
+
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md b/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md index 4accdb693..2dccece96 100644 --- a/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_simplified.md @@ -27,4 +27,3 @@ Scanner Description.... - diff --git a/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md b/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md index 127b655c1..97e70ed1f 100644 --- a/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md +++ b/testdata/messages/reviewcomment/secrets/secret_review_content_standard.md @@ -19,4 +19,4 @@ Scanner Description.... -
+
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md index a9c50edc5..54788f9b8 100644 --- a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md +++ b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_simplified.md @@ -28,4 +28,3 @@ Scanner Description.... - diff --git a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md index 7e4709dec..395e57ce7 100644 --- a/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md +++ b/testdata/messages/reviewcomment/secrets/secret_violation_review_content_standard.md @@ -20,4 +20,4 @@ Scanner Description.... -
+
\ No newline at end of file diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 981ea2032..1c1235a15 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -2,6 +2,7 @@ package outputwriter import ( "fmt" + "sort" "strings" "github.com/jfrog/frogbot/v2/utils/issues" @@ -11,6 +12,7 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/jasutils" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" + "golang.org/x/exp/maps" ) const ( @@ -736,16 +738,7 @@ func getJasIssueDescriptionTable(writer OutputWriter, issues ...formats.SourceCo // For Jas we show description for each unique rule func getJasFullDescription(violations bool, writer OutputWriter, generateRuleTable func(formats.ScannerInfo, OutputWriter) *MarkdownTableBuilder, issues ...formats.SourceCodeRow) string { // Group by scanner info - rulesInfo := map[string]formats.ScannerInfo{} - codeFlows := map[string][][]formats.Location{} - for _, issue := range issues { - if _, ok := rulesInfo[issue.RuleId]; ok { - codeFlows[issue.RuleId] = append(codeFlows[issue.RuleId], issue.CodeFlow...) - continue - } - rulesInfo[issue.RuleId] = issue.ScannerInfo - codeFlows[issue.RuleId] = issue.CodeFlow - } + rulesInfo, codeFlows := groupIssuesByScanner(issues...) // Write the details for each rule var contentBuilder strings.Builder for _, info := range rulesInfo { @@ -753,19 +746,44 @@ func getJasFullDescription(violations bool, writer OutputWriter, generateRuleTab if v, ok := codeFlows[info.RuleId]; ok { scannerCodeFlows = v } - if len(rulesInfo) == 1 { - WriteContent(&contentBuilder, - writer.MarkAsDetails("Full description", 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer)), - // TODO: should remove this? - codeFlowsReviewContent(scannerCodeFlows, writer), - ) - break + title := "Full description" + if len(rulesInfo) > 1 { + title = getJasDetailsIdentifier(info) } - WriteContent(&contentBuilder, writer.MarkAsDetails(getJasDetailsIdentifier(info), 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer, scannerCodeFlows...))) + WriteContent(&contentBuilder, writer.MarkAsDetails(title, 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer, scannerCodeFlows...))) + + // if len(rulesInfo) == 1 { + // WriteContent(&contentBuilder, + // writer.MarkAsDetails("Full description", 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer)), + // // TODO: should remove this? + // codeFlowsReviewContent(scannerCodeFlows, writer), + // ) + // break + // } + // WriteContent(&contentBuilder, writer.MarkAsDetails(getJasDetailsIdentifier(info), 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer, scannerCodeFlows...))) } return contentBuilder.String() } +func groupIssuesByScanner(issues ...formats.SourceCodeRow) (rulesInfo []formats.ScannerInfo, codeFlows map[string][][]formats.Location) { + rulesInfoMap := map[string]formats.ScannerInfo{} + codeFlows = map[string][][]formats.Location{} + for _, issue := range issues { + if _, ok := rulesInfoMap[issue.RuleId]; ok { + codeFlows[issue.RuleId] = append(codeFlows[issue.RuleId], issue.CodeFlow...) + continue + } + rulesInfoMap[issue.RuleId] = issue.ScannerInfo + codeFlows[issue.RuleId] = issue.CodeFlow + } + rulesInfo = maps.Values(rulesInfoMap) + // Sort by rule id + sort.Slice(rulesInfo, func(i, j int) bool { + return rulesInfo[i].RuleId < rulesInfo[j].RuleId + }) + return +} + func getJasDetailsIdentifier(info formats.ScannerInfo) string { id := info.RuleId if info.ScannerShortDescription != "" { From b46de64b7dffe02a3c33b944324316d73b863944 Mon Sep 17 00:00:00 2001 From: attiasas Date: Sun, 5 Jan 2025 15:52:23 +0300 Subject: [PATCH 34/34] fix --- go.mod | 8 ++------ go.sum | 4 ++-- utils/outputwriter/outputcontent.go | 10 ---------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 6d9c20a9d..e253adb73 100644 --- a/go.mod +++ b/go.mod @@ -118,10 +118,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -//attiasas:simple_json_tech_use -replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-security v0.0.0-20250105112737-1e799179a1d1 - -// replace github.com/jfrog/jfrog-cli-security => github.com/eranturgeman/jfrog-cli-security v0.0.0-20241211092436-7d612014c0cc +// attiasas:simple_json_tech_use +replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-security v0.0.0-20250105124238-f947480303fa // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev @@ -129,6 +127,4 @@ replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-sec replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1 -// replace github.com/jfrog/jfrog-client-go => github.com/attiasas/jfrog-client-go v0.0.0-20241202121042-ba0c6c74db7a - // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go dev diff --git a/go.sum b/go.sum index 8cea2a56a..1a7224a8b 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/attiasas/jfrog-cli-security v0.0.0-20250105112737-1e799179a1d1 h1:/tAdaK+vr0PkIlxnxfl4Pw9k+WDAV9XkG/GquP9Y2Xs= -github.com/attiasas/jfrog-cli-security v0.0.0-20250105112737-1e799179a1d1/go.mod h1:lwaXWYJirqBhNmQ148xpM2h4s9xGLaoG50T2/Ax2jM0= +github.com/attiasas/jfrog-cli-security v0.0.0-20250105124238-f947480303fa h1:3Ne4DrpOGT3QJx17odlVl+WHIfqLjGd6SAa2ENWel0E= +github.com/attiasas/jfrog-cli-security v0.0.0-20250105124238-f947480303fa/go.mod h1:lwaXWYJirqBhNmQ148xpM2h4s9xGLaoG50T2/Ax2jM0= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 1c1235a15..f68e8b69a 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -751,16 +751,6 @@ func getJasFullDescription(violations bool, writer OutputWriter, generateRuleTab title = getJasDetailsIdentifier(info) } WriteContent(&contentBuilder, writer.MarkAsDetails(title, 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer, scannerCodeFlows...))) - - // if len(rulesInfo) == 1 { - // WriteContent(&contentBuilder, - // writer.MarkAsDetails("Full description", 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer)), - // // TODO: should remove this? - // codeFlowsReviewContent(scannerCodeFlows, writer), - // ) - // break - // } - // WriteContent(&contentBuilder, writer.MarkAsDetails(getJasDetailsIdentifier(info), 3, getJasRuleFullDescription(violations, info.ScannerDescription, generateRuleTable(info, writer), writer, scannerCodeFlows...))) } return contentBuilder.String() }