@conterra/vuln-scan 0.0.24 → 0.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vuln-scan.js CHANGED
@@ -22,7 +22,7 @@ ${(0,Ai.default)(t.description)}
22
22
 
23
23
  ${t.refs.map(o=>`* <${o}>`).join(`
24
24
  `)}
25
- `}var qe=require("fs/promises");async function $i(t,e,r){if("sbomFile"in t)return await(0,qe.copyFile)(t.sbomFile,e),t.sbomFile=e,t;let i=await r.downloadArtifact(t.sbomMavenCoordinates);return await(0,qe.writeFile)(e,i),{...t,sbomFile:e}}var _e=class{constructor(e,r){this.repo=e;this.authn=r}async downloadArtifact(e){let r=this.toArtifactInfo(e);if(this.isSnapshotVersion(r.version)&&!r.timestamp){let o=await this.fetchLatestSnapshotTimestamp(r);r={...r,timestamp:o}}let i=this.toArtifactUrl(r),n=await pe(i,this.authn);return Buffer.from(await n.arrayBuffer())}async fetchLatestSnapshotTimestamp(e){let r=`${this.toArtifactBaseUrl(e)}/maven-metadata.xml`,n=await(await pe(r,this.authn)).text();return ks(n,e)}toArtifactUrl(e){let r=this.toArtifactBaseUrl(e),i=this.isSnapshotVersion(e.version)&&e.timestamp?e.version.replace(/SNAPSHOT$/,e.timestamp):e.version;return`${r}/${e.artifactId}-${i}-${e.classifier}.${e.extension}`}toArtifactBaseUrl(e){return`${this.repo}/${e.groupId.replace(/\./g,"/")}/${e.artifactId}/${e.version}`}isSnapshotVersion(e){return e.endsWith("-SNAPSHOT")}toArtifactInfo(e){let r=e.split(":");return{groupId:r[0],artifactId:r[1],version:r[2],classifier:r[3],extension:"json"}}};function ks(t,e){if(e.classifier&&e.extension){let n=t.match(new RegExp(`<snapshotVersion>\\s*<classifier>${e.classifier}</classifier>\\s*<extension>${e.extension}</extension>\\s*<value>([^<]+)</value>\\s*<updated>[^<]+</updated>\\s*</snapshotVersion>`)),o=e.version.replace(/SNAPSHOT$/,"");if(n?.[1])return n[1].replace(o,"")}let r=t.match(/<timestamp>([^<]+)<\/timestamp>/),i=t.match(/<buildNumber>([^<]+)<\/buildNumber>/);if(!r||!i)throw new Error(`Failed to get timestamp and build number from ${t} for artifact '${e.artifactId}'`);return`${r[1]}-${i[1]}`}var Ue=require("fs/promises"),Lt=require("path");var Pt=class{constructor(e){this.location=e}async read(){let e=await U(this.location);if(!e)throw new Error("Invalid VEX file");return e}async write(e){await et(this.location,e)}async delete(){await(0,Ue.unlink)(this.location)}};async function Xe(t,e){for(let r of await(0,Ue.readdir)(t,{withFileTypes:!0}))r.name.endsWith(".json")?await e(new Pt((0,Lt.resolve)(r.parentPath,r.name))):r.isDirectory()&&await Xe((0,Lt.resolve)(r.parentPath,r.name),e)}function Et(t){let e=t.vulnerability.name;return new Set([e,...t.vulnerability.aliases??[]])}var de=class t{statements=[];filteredToProducts=[];static async fromUrl(e,r,i=new t){let o=await(await pe(e,r)).json();return i.add(o),i}static async fromFiles(e,r=new t){return await Xe(e,async i=>{let n=await i.read();r.add(n)}),r}add(e){Ds(e);let r=e.timestamp;for(let i of e.statements){i.timestamp=i.timestamp??r;let n=Date.parse(i.timestamp),o=i.vulnerability.name,a=Et(i),c=new Set(i.products?.map(l=>l["@id"]).filter(l=>!!l)??[]),u=new kt(n,o,a,c,i);this.statements.push(u)}this.sortStatementsByTimestampNewestFirst()}filterByProduct(e){if(this.assertCorrectProductId(e),this.filteredToProducts.length)return this;let r=new t;return r.filteredToProducts=e.slice(0),r.statements=this.statements.filter(i=>i.matchesAtLeastOneProduct(e)),r}findNotMatchingVulnerabilities(e,r){let i=this.filterByProduct(r??this.filteredToProducts),n=new Set;for(let o of i.statements)n.add(o.vulnName);for(let o of e)n.delete(o);return Array.from(n)}newestVulnerabilityStatement(e,r){let i=this.filterByProduct(r??this.filteredToProducts);for(let n of i.statements)if(n.matchesVulnerability(e))return this.reduceToProductInformation(n,i.filteredToProducts[0])}toVexFileForProduct(e){let r=this.filterByProduct(e??this.filteredToProducts),i=r.statements.map(n=>this.reduceToProductInformation(n,r.filteredToProducts[0]));return this.toVexFile(i)}toVexFile(e){let r=e.filter(n=>n.status!=="under_investigation");if(r.length===0)return;this.sortVexStatementsByTimestampNewestFirst(r);let i=r[0].timestamp;return{"@context":"https://openvex.dev/ns/v0.2.0","@id":"https://openvex.dev/docs/public/vex-fc763e6eb63bfbcda20b7cbbddd4e9f8feaaa317fe8d8a6f51a5663a1180343e",author:"conterra",timestamp:i,last_updated:i,version:1,statements:r}}assertCorrectProductId(e){if(!e||e.length===0)throw new Error("Product ID must be provided");if(this.filteredToProducts.length&&!Es(this.filteredToProducts,e))throw new Error("Cannot filter to a different product")}reduceToProductInformation(e,r){let i={timestamp:new Date(e.timestamp).toISOString(),...e.statement};if(!r)return i;let n=i.products?.filter(o=>o["@id"]===r);return n?.length||(n=[{"@id":r}]),{...i,products:n}}sortStatementsByTimestampNewestFirst(){this.statements.sort((e,r)=>r.timestamp-e.timestamp)}sortVexStatementsByTimestampNewestFirst(e){e.sort((r,i)=>Date.parse(i.timestamp)-Date.parse(r.timestamp))}};function Es(t,e){return t.length===e.length&&t.every((r,i)=>r===e[i])}var kt=class{constructor(e,r,i,n,o){this.timestamp=e;this.vulnName=r;this.vulnAliases=i;this.products=n;this.statement=o}matchesProduct(e){return this.products.has(e)}matchesAtLeastOneProduct(e){return e.some(this.matchesProduct.bind(this))}matchesVulnerability(e){return this.vulnAliases.has(e)}};function Ds(t){if(t?.["@context"]!=="https://openvex.dev/ns/v0.2.0")throw new Error("Invalid VEX file format")}var Ge=require("path"),_i=require("fs/promises");var Be=class{constructor(e,r,i){this.project=e;this.outputDir=r;this.useSubDirForProject=i}async lookupOutputFileForWriting(e){let r=this.useSubDirForProject?`${this.project.name}-${this.project.version}`:"",i=`${this.project.name}-${this.project.version}-`,n=this.getFileSuffix(e),o=(0,Ge.resolve)(this.outputDir,r,`${i}${n}`),a=(0,Ge.dirname)(o);return await ne(a)||await(0,_i.mkdir)(a,{recursive:!0}),o}getFileSuffix(e){switch(e){case"grype-results":return"scan-grype-results.json";case"trivy-results":return"scan-trivy-results.json";case"ossindex-results":return"scan-ossindex-results.json";case"aggregate-results":return"scan-aggregate-results.json";case"aggregated-sarif-results":return"scan-aggregated-results.sarif.json";case"project-cyclonedx-sbom-file":return"sbom.cdx.json";case"project-openvex-file":return"openvex.vex.json";default:throw new Error(`Unknown output file key: ${e}`)}}};var Ot=require("zx");var We=require("path"),qi="anchore/grype:latest",Dt=process.getuid?.()??0;async function Ui(t,e){e&&console.log(`Initializing Grype DB in cache directory: ${t} using user ID: ${Dt}`);let r=await Ot.$`docker run --rm -v '${t}:/tmp/.cache' -e GRYPE_DB_CACHE_DIR=/tmp/.cache -e TMPDIR=/tmp --user ${Dt} ${qi} db update`.nothrow();if(r.stdout&&console.log(` Grype DB Update: ${r.stdout}`),r.stderr&&console.log(` Grype DB Update error: ${r.stderr}`),r.exitCode!==0)throw new Error(`Grype DB update failed with exit code ${r.exitCode}: ${r.stderr}`)}async function Xi(t,e,r,i,n,o){let a=(0,We.dirname)(e),c=(0,We.basename)(e),u=[];n||u.push("--quiet");let l=await Ot.$`docker run --rm -v '${t}:/tmp/.cache' --user ${Dt} -v '${a}:/tmp/output' -v '${r.sbomFile}:/tmp/input.json' -e GRYPE_DB_CACHE_DIR=/tmp/.cache -e TMPDIR=/tmp -e GRYPE_DB_AUTO_UPDATE=false ${qi} sbom:/tmp/input.json -o json --file '/tmp/output/${c}' --by-cve ${u}`.nothrow();if(l.stdout&&o(` Scan log: ${l.stdout}`),l.stderr&&o(` Scan error: ${l.stderr}`),l.exitCode!==0)throw new Error(`Grype scan failed with exit code ${l.exitCode}: ${l.stderr}`);o(` Grype output file: ${e}`);let s=await U(e);Os(i,s)}function Os(t,e){if(e)for(let r of e.matches){let i=js(r);t.addVulnerability(i,"grype")}}function js(t){let e=t.vulnerability,r=e.id,i=J(t.artifact.purl),n=J(e.dataSource),o=Fs(e.severity);return{id:r,severity:o,scannerSeverity:{grype:o},title:"",description:e.description,components:[i],detectedBy:["grype"],refs:[n]}}function Fs(t){switch(t){case"Negligible":return"LOW";case"Low":return"LOW";case"Medium":return"MEDIUM";case"High":return"HIGH";case"Critical":return"CRITICAL";default:return"UNKNOWN"}}var Bi=require("fs/promises");async function Gi(t,e,r,i,n,o){let a={},c="";r.user&&r.token&&(a.Authorization=`Basic ${Buffer.from(`${r.user}:${r.token}`).toString("base64")}`,c="/authorized");let u=`https://ossindex.sonatype.org/api/v3${c}/component-report`,l=await Ts(e.sbomFile),s=new Set(l.map(f=>f.purl.replace(/\?.*$/,"")));As(s);let m=Array.from(s),p=[];for(let f=0;f<m.length;f+=128){let d=JSON.stringify({coordinates:m.slice(f,f+128)});n&&o(` Sonatype OssIndex request ${u} with body: ${d}`);let g=await(await fetch(u,{method:"POST",headers:{...a,"Content-Type":"application/vnd.ossindex.component-report-request.v1+json"},body:d})).json();Array.isArray(g)?p.push(...g.filter(b=>b.vulnerabilities.length>0)):o(` Sonatype OssIndex Error: ${g.code} - ${g.message}`)}await(0,Bi.writeFile)(t,JSON.stringify(p,null,2)),o(` Sonatype OssIndex output file: ${t}`),Ns(i,p)}async function Ts(t){return(await U(t)).components.filter(r=>Is(r)&&!Cs(r))}function Is(t){return!!t.purl}function Cs(t){return t.group?t.group.startsWith("de.conterra")||t.group.startsWith("org.n52.security"):!1}function Ns(t,e){if(e)for(let r of e)for(let i of r.vulnerabilities){let n=Ms(i,r.coordinates);t.addVulnerability(n,"ossindex")}}function Ms(t,e){let r=t.cve||t.id,i=J(e),n=J(t.reference),o=Rs(t.cvssScore);return{id:r,severity:o,scannerSeverity:{ossindex:o},title:t.title,description:t.description,components:[i],detectedBy:["ossindex"],refs:n?[n]:[]}}function Rs(t){return t===0?"UNKNOWN":t<4?"LOW":t<7?"MEDIUM":t<9?"HIGH":"CRITICAL"}function As(t){let e=Array.from(t);for(let r of e){let i=r.replace(/(@[^-]+)-.*$/g,"$1");t.add(i)}}var jt=require("zx");var Je=require("path"),Wi="aquasec/trivy:latest";async function Ji(t,e,r){let i=[];e&&(i.push("-e"),i.push(`GITHUB_TOKEN=${e}`));let n=[];n.push("--db-repository"),n.push("public.ecr.aws/aquasecurity/trivy-db,aquasec/trivy-db,ghcr.io/aquasecurity/trivy-db"),r||n.push("--quiet");let o=await jt.$`docker run --rm -v '${t}:/.cache' ${i} ${Wi} ${n} --cache-dir '/.cache' sbom --download-db-only`.nothrow();if(!o.stdout&&!o.stderr&&console.log(" Trivy DB Update: finished"),o.stdout&&console.log(` Trivy DB Update: ${o.stdout}`),o.stderr&&console.log(` Trivy DB Update error: ${o.stderr}`),o.exitCode!==0)throw new Error(`Trivy DB update failed with exit code ${o.exitCode}: ${o.stderr}`)}async function Hi(t,e,r,i,n,o){let a=(0,Je.dirname)(e),c=(0,Je.basename)(e),u=[];n||u.push("--quiet");let l=await jt.$`docker run --rm -v '${t}:/.cache' -v '${a}:/tmp/output' -v '${r.sbomFile}:/tmp/input.json' ${Wi} sbom --cache-dir '/.cache' --format json --output '/tmp/output/${c}' ${u} --skip-db-update /tmp/input.json`.nothrow();if(l.stdout&&o(` Scan log: ${l.stdout}`),l.stderr&&o(` Scan error: ${l.stderr}`),l.exitCode!==0)throw new Error(`Trivy scan failed with exit code ${l.exitCode}: ${l.stderr}`);o(` Trivy output file: ${e}`);let s=await U(e);Vs(i,s)}function Vs(t,e){if(e){for(let r of e.Results)if(r.Vulnerabilities)for(let i of r.Vulnerabilities){let n=$s(i);t.addVulnerability(n,"trivy")}}}function $s(t){let e=t.VulnerabilityID,r=J(t.PkgIdentifier.PURL),i=J(t.PrimaryURL),n=_s(t.Severity);return{id:e,severity:n,scannerSeverity:{trivy:n},title:t.Title,description:t.Description,components:[r],detectedBy:["trivy"],refs:[i]}}function _s(t){switch(t){case"UNKNOWN":case"LOW":case"MEDIUM":case"HIGH":case"CRITICAL":return t;default:return"UNKNOWN"}}var He=class{constructor(e,r,i){this.scannerNames=e;this.cacheDir=r;this.options=i}async init(e){let r=[];for(let i of this.scannerNames)switch(i){case"grype":r.push(Ui(this.cacheDir,e));break;case"trivy":r.push(Ji(this.cacheDir,this.options.trivyGithubToken,e));break;case"ossindex":continue}await Promise.all(r)}async scanProject(e,r,i,n,o){let a=[];for(let c of this.scannerNames)a.push(this.scanProjectBy(c,e,r,this.cacheDir,i,this.options.ossIndexApiUser,this.options.ossIndexApiToken,n,o));await Promise.all(a)}async scanProjectBy(e,r,i,n,o,a,c,u,l){switch(e){case"grype":return Xi(n,await i.lookupOutputFileForWriting("grype-results"),r,o,u,l);case"trivy":return Hi(n,await i.lookupOutputFileForWriting("trivy-results"),r,o,u,l);case"ossindex":return Gi(await i.lookupOutputFileForWriting("ossindex-results"),r,{user:a,token:c},o,u,l)}}};async function Qi(t,e,r){await Xe(t,async i=>{let n=await i.read(),o=!1,a=n.statements,c=[];for(let u of a){if(r&&!Et(u).has(r)||!u.products)continue;let l=u.products.findIndex(s=>s["@id"]===e);l!==-1&&(u.products.splice(l,1),o=!0,u.products.length===0&&c.push(u))}if(o){for(let u of c){let l=a.indexOf(u);a.splice(l,1)}if(a.length===0){console.info(`Remove ${e} from ${i.location} and delete file.`),await i.delete();return}console.info(`Remove ${e} from ${i.location}`),await i.write(n)}})}Qe.os.type().startsWith("Windows")&&(0,Qe.usePowerShell)();async function Yi(t){let e=await it(t),r=e.cacheDir;await ne(r)||await(0,ae.mkdir)(r);let i=e.outDir;await ne(i)&&await tt(i),await(0,ae.mkdir)(i);let n=await Gs(e,{user:t.vexUrlsUser,pw:t.vexUrlsPw}),o=new _e(e.mavenRepo,{user:t.mavenRepoUser,pw:t.mavenRepoPw}),a=Ws(e,t,r),c=Date.now();console.log("Initializing scanner engines ..."),await a.init(t.verbose),console.log(`Scanner engines initialization took ${Date.now()-c}ms
25
+ `}var qe=require("fs/promises");async function $i(t,e,r){if("sbomFile"in t)return await(0,qe.copyFile)(t.sbomFile,e),t.sbomFile=e,t;let i=await r.downloadArtifact(t.sbomMavenCoordinates);return await(0,qe.writeFile)(e,i),{...t,sbomFile:e}}var _e=class{constructor(e,r){this.repo=e;this.authn=r}async downloadArtifact(e){let r=this.toArtifactInfo(e);if(this.isSnapshotVersion(r.version)&&!r.timestamp){let o=await this.fetchLatestSnapshotTimestamp(r);r={...r,timestamp:o}}let i=this.toArtifactUrl(r),n=await pe(i,this.authn);return Buffer.from(await n.arrayBuffer())}async fetchLatestSnapshotTimestamp(e){let r=`${this.toArtifactBaseUrl(e)}/maven-metadata.xml`,n=await(await pe(r,this.authn)).text();return ks(n,e)}toArtifactUrl(e){let r=this.toArtifactBaseUrl(e),i=this.isSnapshotVersion(e.version)&&e.timestamp?e.version.replace(/SNAPSHOT$/,e.timestamp):e.version;return`${r}/${e.artifactId}-${i}-${e.classifier}.${e.extension}`}toArtifactBaseUrl(e){return`${this.repo}/${e.groupId.replace(/\./g,"/")}/${e.artifactId}/${e.version}`}isSnapshotVersion(e){return e.endsWith("-SNAPSHOT")}toArtifactInfo(e){let r=e.split(":");return{groupId:r[0],artifactId:r[1],version:r[2],classifier:r[3],extension:"json"}}};function ks(t,e){if(e.classifier&&e.extension){let n=t.match(new RegExp(`<snapshotVersion>\\s*<classifier>${e.classifier}</classifier>\\s*<extension>${e.extension}</extension>\\s*<value>([^<]+)</value>\\s*<updated>[^<]+</updated>\\s*</snapshotVersion>`)),o=e.version.replace(/SNAPSHOT$/,"");if(n?.[1])return n[1].replace(o,"")}let r=t.match(/<timestamp>([^<]+)<\/timestamp>/),i=t.match(/<buildNumber>([^<]+)<\/buildNumber>/);if(!r||!i)throw new Error(`Failed to get timestamp and build number from ${t} for artifact '${e.artifactId}'`);return`${r[1]}-${i[1]}`}var Ue=require("fs/promises"),Lt=require("path");var Pt=class{constructor(e){this.location=e}async read(){let e=await U(this.location);if(!e)throw new Error("Invalid VEX file");return e}async write(e){await et(this.location,e)}async delete(){await(0,Ue.unlink)(this.location)}};async function Xe(t,e){for(let r of await(0,Ue.readdir)(t,{withFileTypes:!0}))r.name.endsWith(".json")?await e(new Pt((0,Lt.resolve)(r.parentPath,r.name))):r.isDirectory()&&await Xe((0,Lt.resolve)(r.parentPath,r.name),e)}function Et(t){let e=t.vulnerability.name;return new Set([e,...t.vulnerability.aliases??[]])}var de=class t{statements=[];filteredToProducts=[];static async fromUrl(e,r,i=new t){let o=await(await pe(e,r)).json();return i.add(o),i}static async fromFiles(e,r=new t){return await Xe(e,async i=>{let n=await i.read();r.add(n)}),r}add(e){Ds(e);let r=e.timestamp;for(let i of e.statements){i.timestamp=i.timestamp??r;let n=Date.parse(i.timestamp),o=i.vulnerability.name,a=Et(i),c=new Set(i.products?.map(l=>l["@id"]).filter(l=>!!l)??[]),u=new kt(n,o,a,c,i);this.statements.push(u)}this.sortStatementsByTimestampNewestFirst()}filterByProduct(e){if(this.assertCorrectProductId(e),this.filteredToProducts.length)return this;let r=new t;return r.filteredToProducts=e.slice(0),r.statements=this.statements.filter(i=>i.matchesAtLeastOneProduct(e)),r}findNotMatchingVulnerabilities(e,r){let i=this.filterByProduct(r??this.filteredToProducts),n=new Set;for(let o of i.statements)n.add(o.vulnName);for(let o of e)n.delete(o);return Array.from(n)}newestVulnerabilityStatement(e,r){let i=this.filterByProduct(r??this.filteredToProducts);for(let n of i.statements)if(n.matchesVulnerability(e))return this.reduceToProductInformation(n,i.filteredToProducts[0])}toVexFileForProduct(e){let r=this.filterByProduct(e??this.filteredToProducts),i=r.statements.map(n=>this.reduceToProductInformation(n,r.filteredToProducts[0]));return this.toVexFile(i)}toVexFile(e){let r=e.filter(n=>n.status!=="under_investigation");if(r.length===0)return;this.sortVexStatementsByTimestampNewestFirst(r);let i=r[0].timestamp;return{"@context":"https://openvex.dev/ns/v0.2.0","@id":"https://openvex.dev/docs/public/vex-fc763e6eb63bfbcda20b7cbbddd4e9f8feaaa317fe8d8a6f51a5663a1180343e",author:"conterra",timestamp:i,last_updated:i,version:1,statements:r}}assertCorrectProductId(e){if(!e||e.length===0)throw new Error("Product ID must be provided");if(this.filteredToProducts.length&&!Es(this.filteredToProducts,e))throw new Error("Cannot filter to a different product")}reduceToProductInformation(e,r){let i={timestamp:new Date(e.timestamp).toISOString(),...e.statement};if(!r)return i;let n=i.products?.filter(o=>o["@id"]===r);return n?.length||(n=[{"@id":r}]),{...i,products:n}}sortStatementsByTimestampNewestFirst(){this.statements.sort((e,r)=>r.timestamp-e.timestamp)}sortVexStatementsByTimestampNewestFirst(e){e.sort((r,i)=>Date.parse(i.timestamp)-Date.parse(r.timestamp))}};function Es(t,e){return t.length===e.length&&t.every((r,i)=>r===e[i])}var kt=class{constructor(e,r,i,n,o){this.timestamp=e;this.vulnName=r;this.vulnAliases=i;this.products=n;this.statement=o}matchesProduct(e){return this.products.has(e)}matchesAtLeastOneProduct(e){return e.some(this.matchesProduct.bind(this))}matchesVulnerability(e){return this.vulnAliases.has(e)}};function Ds(t){if(t?.["@context"]!=="https://openvex.dev/ns/v0.2.0")throw new Error("Invalid VEX file format")}var Ge=require("path"),_i=require("fs/promises");var Be=class{constructor(e,r,i){this.project=e;this.outputDir=r;this.useSubDirForProject=i}async lookupOutputFileForWriting(e){let r=this.useSubDirForProject?`${this.project.name}-${this.project.version}`:"",i=`${this.project.name}-${this.project.version}-`,n=this.getFileSuffix(e),o=(0,Ge.resolve)(this.outputDir,r,`${i}${n}`),a=(0,Ge.dirname)(o);return await ne(a)||await(0,_i.mkdir)(a,{recursive:!0}),o}getFileSuffix(e){switch(e){case"grype-results":return"scan-grype-results.json";case"trivy-results":return"scan-trivy-results.json";case"ossindex-results":return"scan-ossindex-results.json";case"aggregate-results":return"scan-aggregate-results.json";case"aggregated-sarif-results":return"scan-aggregated-results.sarif.json";case"project-cyclonedx-sbom-file":return"sbom.cdx.json";case"project-openvex-file":return"openvex.vex.json";default:throw new Error(`Unknown output file key: ${e}`)}}};var Ot=require("zx");var We=require("path"),qi="anchore/grype:latest",Dt=process.getuid?.()??0;async function Ui(t,e){e&&console.log(`Initializing Grype DB in cache directory: ${t} using user ID: ${Dt}`);let r=await Ot.$`docker run --rm -v '${t}:/tmp/.cache' -e GRYPE_DB_CACHE_DIR=/tmp/.cache -e TMPDIR=/tmp --user ${Dt} ${qi} db update`.nothrow();if(r.stdout&&console.log(` Grype DB Update: ${r.stdout}`),r.stderr&&console.log(` Grype DB Update error: ${r.stderr}`),r.exitCode!==0)throw new Error(`Grype DB update failed with exit code ${r.exitCode}: ${r.stderr}`)}async function Xi(t,e,r,i,n,o){let a=(0,We.dirname)(e),c=(0,We.basename)(e),u=[];n||u.push("--quiet");let l=await Ot.$`docker run --rm -v '${t}:/tmp/.cache' --user ${Dt} -v '${a}:/tmp/output' -v '${r.sbomFile}:/tmp/input.json' -e GRYPE_DB_CACHE_DIR=/tmp/.cache -e TMPDIR=/tmp -e GRYPE_DB_AUTO_UPDATE=false ${qi} sbom:/tmp/input.json -o json --file '/tmp/output/${c}' --by-cve ${u}`.nothrow();if(l.stdout&&o(` Scan log: ${l.stdout}`),l.stderr&&o(` Scan error: ${l.stderr}`),l.exitCode!==0)throw new Error(`Grype scan failed with exit code ${l.exitCode}: ${l.stderr}`);o(` Grype output file: ${e}`);let s=await U(e);Os(i,s)}function Os(t,e){if(e)for(let r of e.matches){let i=js(r);t.addVulnerability(i,"grype")}}function js(t){let e=t.vulnerability,r=e.id,i=J(t.artifact.purl),n=J(e.dataSource),o=Fs(e.severity);return{id:r,severity:o,scannerSeverity:{grype:o},title:"",description:e.description,components:[i],detectedBy:["grype"],refs:[n]}}function Fs(t){switch(t){case"Negligible":return"LOW";case"Low":return"LOW";case"Medium":return"MEDIUM";case"High":return"HIGH";case"Critical":return"CRITICAL";default:return"UNKNOWN"}}var Bi=require("fs/promises");async function Gi(t,e,r,i,n,o){let a={},c="";r.user&&r.token&&(a.Authorization=`Basic ${Buffer.from(`${r.user}:${r.token}`).toString("base64")}`,c="/authorized");let u=`https://ossindex.sonatype.org/api/v3${c}/component-report`,l=await Ts(e.sbomFile),s=new Set(l.map(f=>f.purl.replace(/\?.*$/,"")));As(s);let m=Array.from(s),p=[];for(let f=0;f<m.length;f+=128){let d=JSON.stringify({coordinates:m.slice(f,f+128)});n&&o(` Sonatype OssIndex request ${u} with body: ${d}`);let g=await(await fetch(u,{method:"POST",headers:{...a,"Content-Type":"application/vnd.ossindex.component-report-request.v1+json"},body:d})).json();Array.isArray(g)?p.push(...g.filter(b=>b.vulnerabilities.length>0)):o(` Sonatype OssIndex Error: ${g.code} - ${g.message}`)}await(0,Bi.writeFile)(t,JSON.stringify(p,null,2)),o(` Sonatype OssIndex output file: ${t}`),Ns(i,p)}async function Ts(t){return(await U(t)).components.filter(r=>Is(r)&&!Cs(r))}function Is(t){return!!t.purl}function Cs(t){return t.group?t.group.startsWith("de.conterra")||t.group.startsWith("org.n52.security"):!1}function Ns(t,e){if(e)for(let r of e)for(let i of r.vulnerabilities){let n=Ms(i,r.coordinates);t.addVulnerability(n,"ossindex")}}function Ms(t,e){let r=t.cve||t.id,i=J(e),n=J(t.reference),o=Rs(t.cvssScore);return{id:r,severity:o,scannerSeverity:{ossindex:o},title:t.title,description:t.description,components:[i],detectedBy:["ossindex"],refs:n?[n]:[]}}function Rs(t){return t===0?"UNKNOWN":t<4?"LOW":t<7?"MEDIUM":t<9?"HIGH":"CRITICAL"}function As(t){let e=Array.from(t);for(let r of e){let i=r.replace(/(@[^-]+)-.*$/g,"$1");t.add(i)}}var jt=require("zx");var Je=require("path"),Wi="aquasec/trivy:latest";async function Ji(t,e,r){let i=[];e&&(i.push("-e"),i.push(`GITHUB_TOKEN=${e}`));let n=[];n.push("--db-repository"),n.push("public.ecr.aws/aquasecurity/trivy-db,aquasec/trivy-db,ghcr.io/aquasecurity/trivy-db"),r||n.push("--quiet");let o=await jt.$`docker run --rm -v '${t}:/.cache' ${i} ${Wi} ${n} --cache-dir '/.cache' sbom --download-db-only`.nothrow();if(!o.stdout&&!o.stderr&&console.log(" Trivy DB Update: finished"),o.stdout&&console.log(` Trivy DB Update: ${o.stdout}`),o.stderr&&console.log(` Trivy DB Update error: ${o.stderr}`),o.exitCode!==0)throw new Error(`Trivy DB update failed with exit code ${o.exitCode}: ${o.stderr}`)}async function Hi(t,e,r,i,n,o){let a=(0,Je.dirname)(e),c=(0,Je.basename)(e),u=[];n||u.push("--quiet");let l=await jt.$`docker run --rm -v '${t}:/.cache' -v '${a}:/tmp/output' -v '${r.sbomFile}:/tmp/input.json' ${Wi} sbom --cache-dir '/.cache' --format json --output '/tmp/output/${c}' ${u} --skip-db-update /tmp/input.json`.nothrow();if(l.stdout&&o(` Scan log: ${l.stdout}`),l.stderr&&o(` Scan error: ${l.stderr}`),l.exitCode!==0)throw new Error(`Trivy scan failed with exit code ${l.exitCode}: ${l.stderr}`);o(` Trivy output file: ${e}`);let s=await U(e);Vs(i,s)}function Vs(t,e){if(e){for(let r of e.Results??[])if(r.Vulnerabilities)for(let i of r.Vulnerabilities){let n=$s(i);t.addVulnerability(n,"trivy")}}}function $s(t){let e=t.VulnerabilityID,r=J(t.PkgIdentifier.PURL),i=J(t.PrimaryURL),n=_s(t.Severity);return{id:e,severity:n,scannerSeverity:{trivy:n},title:t.Title,description:t.Description,components:[r],detectedBy:["trivy"],refs:[i]}}function _s(t){switch(t){case"UNKNOWN":case"LOW":case"MEDIUM":case"HIGH":case"CRITICAL":return t;default:return"UNKNOWN"}}var He=class{constructor(e,r,i){this.scannerNames=e;this.cacheDir=r;this.options=i}async init(e){let r=[];for(let i of this.scannerNames)switch(i){case"grype":r.push(Ui(this.cacheDir,e));break;case"trivy":r.push(Ji(this.cacheDir,this.options.trivyGithubToken,e));break;case"ossindex":continue}await Promise.all(r)}async scanProject(e,r,i,n,o){let a=[];for(let c of this.scannerNames)a.push(this.scanProjectBy(c,e,r,this.cacheDir,i,this.options.ossIndexApiUser,this.options.ossIndexApiToken,n,o));await Promise.all(a)}async scanProjectBy(e,r,i,n,o,a,c,u,l){switch(e){case"grype":return Xi(n,await i.lookupOutputFileForWriting("grype-results"),r,o,u,l);case"trivy":return Hi(n,await i.lookupOutputFileForWriting("trivy-results"),r,o,u,l);case"ossindex":return Gi(await i.lookupOutputFileForWriting("ossindex-results"),r,{user:a,token:c},o,u,l)}}};async function Qi(t,e,r){await Xe(t,async i=>{let n=await i.read(),o=!1,a=n.statements,c=[];for(let u of a){if(r&&!Et(u).has(r)||!u.products)continue;let l=u.products.findIndex(s=>s["@id"]===e);l!==-1&&(u.products.splice(l,1),o=!0,u.products.length===0&&c.push(u))}if(o){for(let u of c){let l=a.indexOf(u);a.splice(l,1)}if(a.length===0){console.info(`Remove ${e} from ${i.location} and delete file.`),await i.delete();return}console.info(`Remove ${e} from ${i.location}`),await i.write(n)}})}Qe.os.type().startsWith("Windows")&&(0,Qe.usePowerShell)();async function Yi(t){let e=await it(t),r=e.cacheDir;await ne(r)||await(0,ae.mkdir)(r);let i=e.outDir;await ne(i)&&await tt(i),await(0,ae.mkdir)(i);let n=await Gs(e,{user:t.vexUrlsUser,pw:t.vexUrlsPw}),o=new _e(e.mavenRepo,{user:t.mavenRepoUser,pw:t.mavenRepoPw}),a=Ws(e,t,r),c=Date.now();console.log("Initializing scanner engines ..."),await a.init(t.verbose),console.log(`Scanner engines initialization took ${Date.now()-c}ms
26
26
  `);let u=Js(t),l=e.projects,s=new Map,m=Date.now();console.log("Scanning projects ...");let p=1,f=0,d=[];for(let g of l){if(!u(g))continue;f>=p&&(await Promise.race(d),--f);let b=new Be(g,i,e.createSubDirsForProject),F=[],T=ie=>{F.push(ie)};d.push(qs(g,b,o,a,n,T,s,t,F)),++f}await Promise.all(d),console.log(`Scanning took ${Date.now()-m}ms`),t.autoRemoveUnusedStatements&&await Hs(s,e.vexDir);let S=Us(s,e.reportNoLongerDetectedVulnerabilities);if(S.hasError)return 1;if(S.hasNewVuln){if(e.onNewVulnerabilities==="fail")return 1;e.onNewVulnerabilities==="warn"&&(console.log("##vso[task.logissue type=warning;]NEW_VULNERABILITIES_DETECTED"),console.log("##vso[task.complete result=SucceededWithIssues;]"))}if(S.hasNoLongerDetectedVuln){if(e.onNoLongerDetectedVulnerabilities==="fail")return 1;e.onNoLongerDetectedVulnerabilities==="warn"&&(console.log("##vso[task.logissue type=warning;]NO_LONGER_DETECTED_VULNERABILITIES"),console.log("##vso[task.complete result=SucceededWithIssues;]"))}return 0}async function qs(t,e,r,i,n,o,a,c,u){try{let{unmatchedVulnerabilities:l,noLongerDetectedVulnerabilities:s}=await Xs(t,e,r,i,n,c.verbose,o);a.set(t.purl,{project:t,newVulns:l,noLongerDetectedVulns:s})}catch(l){let s=l;a.set(t.purl,{project:t,error:`${s}`,newVulns:[],noLongerDetectedVulns:[]})}if(c.verbose)for(let l of u)console.log(l)}function Us(t,e){let r=!1,i=!1,n=!1,o=new Map,a=new Map,c=[];for(let u of t.values()){let l=u.project,s=`${l.name} - ${l.version}`;if(u.error&&(r=!0,c.push({project:s,error:u.error})),l.silent)continue;let p=W[l.minimumSeverity],f=u.newVulns.filter(d=>p<=W[d.severity]);for(let d of f)i=!0,o.has(d.id)||o.set(d.id,{severity:d.severity,projects:[]}),o.get(d.id).projects.push(s);if(e)for(let d of u.noLongerDetectedVulns)n=!0,a.has(d)||a.set(d,[]),a.get(d).push(s)}if(console.log(`
27
27
  Scan results:`),c.length)for(let{project:u,error:l}of c)console.error(` ${u}:`),console.error(` Error: ${l}`);if(o.size){console.log(" New vulnerabilities:");for(let[u,{severity:l,projects:s}]of o.entries()){console.log(` [${l}] ${u} detected in:`);for(let m of s)console.log(` - ${m}`)}}if(a.size){console.log(" Declared but no longer detected vulnerabilities:");for(let[u,l]of a.entries()){console.log(` ${u} was declared in:`);for(let s of l)console.log(` - ${s}`)}}return!r&&!i&&!n&&console.log(" No issues found"),{hasError:r,hasNewVuln:i,hasNoLongerDetectedVuln:n}}async function Xs(t,e,r,i,n,o,a){let c=await $i(t,await e.lookupOutputFileForWriting("project-cyclonedx-sbom-file"),r);a(`Analyzing project: ${c.name} - ${c.version} - ${c.sbomFile}`);let u=new Le;await i.scanProject(c,e,u,o,a);let l=n.filterByProduct([c.purl,...c.purlAliases]),{unmatchedVulnerabilities:s}=u.enhanceWithVexData(l),m=c.detectNoLongerUsedVulnerabilities?l.findNotMatchingVulnerabilities(u.getAllVulnIds()):[];return await Bs(e,c,u,l,a),{unmatchedVulnerabilities:s,noLongerDetectedVulnerabilities:m}}async function Bs(t,e,r,i,n){let o=r.getAllVulnerabilitiesSortedBySeverity(),a=await t.lookupOutputFileForWriting("aggregate-results");await(0,ae.writeFile)(a,JSON.stringify(o,null,2)),n(` Vulnerabilities output file: ${a}`);let c=await t.lookupOutputFileForWriting("aggregated-sarif-results"),u=Vi(o,e);await(0,ae.writeFile)(c,JSON.stringify(u,null,2)),n(` Vuln Sarif output file: ${c}`);let l=i.toVexFileForProduct();if(!l)return;let s=await t.lookupOutputFileForWriting("project-openvex-file");await(0,ae.writeFile)(s,JSON.stringify(l,null,2)),n(` OpenVex file: ${s}`)}async function Gs(t,e){let r=t.vexDir,i;await ne(r)?i=await de.fromFiles(r):i=new de;for(let n of t.vexUrls)await de.fromUrl(n,e,i);return i}function Ws(t,e,r){if(e.scannerEngine)return e.scannerEngine;if(t.scanners.length===0)throw new Error("No scanners configured");return new He(t.scanners,r,e)}function Js(t){let{reduceToProjectName:e,reduceToProjectVersion:r}=t;return i=>!(!i.enabled||e&&(i.name!==e||r&&i.version!==r))}async function Hs(t,e){for(let r of t.values())if(!r.project.silent)for(let i of r.noLongerDetectedVulns)await Qi(e,r.project.purl,i)}var Ki=require("zx");Ki.dotenv.config();var Y=process.env;(async()=>{let t=await Yi({workingDir:process.cwd(),reduceToProjectName:process.argv[2],reduceToProjectVersion:process.argv[3],ossIndexApiUser:Y.OSS_INDEX_API_USER,ossIndexApiToken:Y.OSS_INDEX_API_TOKEN,mavenRepoUser:Y.MAVEN_REPO_USER,mavenRepoPw:Y.MAVEN_REPO_PW,trivyGithubToken:Y.TRIVY_GITHUB_TOKEN,vexUrlsUser:Y.VEX_URLS_USER,vexUrlsPw:Y.VEX_URLS_PW,verbose:Y.VERBOSE==="true",autoRemoveUnusedStatements:Y.AUTO_REMOVE_UNUSED_STATEMENTS==="true"});process.exit(t)})();
28
28
  /*! Bundled license information:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conterra/vuln-scan",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "description": "con terra vulnerability scan process",
5
5
  "author": "con terra GmbH",
6
6
  "license": "Apache-2.0",