@conterra/vuln-scan 0.1.6 → 1.0.8
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/add-project-to-vex.js +2 -584
- package/dist/add-project.js +18 -600
- package/dist/add-statement.js +21 -603
- package/dist/jira-reporter.js +4 -4
- package/dist/remove-project.js +19 -601
- package/dist/vuln-scan.js +16 -598
- package/package.json +3 -3
package/dist/jira-reporter.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var J=require("path"),V=require("zx");var R=require("fs/promises");function E(r,e=""){return O(r,e)}function O(r,e="",n
|
|
3
|
-
`,s+=
|
|
4
|
-
`),
|
|
2
|
+
"use strict";var J=require("path"),V=require("zx");var R=require("fs/promises");function E(r,e=""){return O(r,e,new Set,new Set)}function O(r,e="",n,t){let s=`${e}- ${r.purl}`;if(n.has(r.purl))return s+=" (cycle detected)",s;if(t.has(r.purl))return s+=" (deduplicated)",s;t.add(r.purl),n.add(r.purl);let i=n.size===1;for(let o of r.usedBy){let a=O(o,e+(i?" ":" "),n,t);a!==""&&(s+=`
|
|
3
|
+
`,s+=a)}return n.delete(r.purl),s}async function U(r){let{scanSummaryPath:e,jiraConfigPath:n,jiraUser:t,jiraApiToken:s,dryRun:i=!1}=r,[o,a]=await Promise.all([W(e),q(n)]),c={created:[],skipped:[],errors:[]},u=new I(a.jiraUrl,t,s),C=new Map;for(let[f,T]of Object.entries(o.vulnerabilities)){let K=o.vulnerabilityToProject[f]??[];for(let l of a.issues){let w=K.filter(h=>M(h,l.reportProjects));if(w.length===0)continue;let S=X(T);try{let h=await u.issueExists(l.jiraProject,f);if(h){console.log(`[skip] Issue for ${f} already exists: ${h}`),c.skipped.push(`${l.jiraProject}::${f}`);continue}if(i){console.log(`[dry-run] Would create issue in ${l.jiraProject}: "${S}"`),console.log(` Matching projects: ${w.join(", ")}`),c.created.push(`${l.jiraProject}::${f}`);continue}let b,k=[],v=!1;for(let p of l.addToBoard??[]){let m=p.matchProjects??["*"];if(w.some(d=>M(d,m)))if(p.sprintBoardId!=null){if(!v){v=!0;let d=p.sprintBoardId;if(!C.has(d)){let P=await u.getActiveSprintId(d).catch(H=>(console.warn(`[warn] Could not resolve active sprint for boardId ${d}: ${String(H)}`),null));C.set(d,P)}b=C.get(d)??void 0,p.issueLabels&&k.push(...p.issueLabels)}}else p.issueLabels&&k.push(...p.issueLabels)}let B=[...l.issueLabels??[],...k],j=new Map;if(o.projectsToComponents)for(let p of w){let m=o.projectsToComponents[p]??{},x=[];for(let d of T.components){let P=m[d];P&&x.push(P)}x.length>0&&j.set(p,x)}let _=ne(T,w,j),$=await u.createIssue({projectKey:l.jiraProject,issueType:l.issueType,summary:S,description:_,labels:B.length>0?B:void 0,sprintId:b});if(j.size>0){let p=re(f,j);if(p){let m=`${f}-dependency-trees.md`;await u.attachMarkdown($,m,p),console.log(`[attached] ${m} to ${$}`)}}console.log(`[created] ${$} in ${l.jiraProject}: "${S}"`),c.created.push($)}catch(h){let b=`Failed to process ${f} for project ${l.jiraProject}: ${String(h)}`;console.error(`[error] ${b}`),c.errors.push(b)}}}return c}async function W(r){let e=await(0,R.readFile)(r,"utf8");return JSON.parse(e)}async function q(r){let e=await(0,R.readFile)(r,"utf8"),n=JSON.parse(e);if(!n.issues&&n.issue&&(n.issues=n.issue),!n.jiraUrl||typeof n.jiraUrl!="string")throw new Error("jira-conf.json: missing or invalid 'jiraUrl' field");if(!Array.isArray(n.issues))throw new Error("jira-conf.json: missing or invalid 'issues' array");return n}function G(r){return r.replace(/[.+?^${}()|[\]\\]/g,"\\$&")}function Q(r,e){let n=e.split("*").map(G).join(".*");return new RegExp(`^${n}$`).test(r)}function M(r,e){return e.some(n=>Q(r,n))}function X(r){let e=r.title?`: ${r.title}`:"";return`[${r.severity}] ${r.id}${e}`}function Y(...r){return{type:"doc",version:1,content:r}}function g(r,e){return{type:"heading",attrs:{level:r},content:[{type:"text",text:e}]}}function Z(r){let e=r.split(/\r?\n/),n=[],t=0;for(;t<e.length;){let s=e[t],i=s.match(/^```(\w*)$/);if(i){let c=i[1]??"text",u=[];for(t++;t<e.length&&!e[t].startsWith("```");)u.push(e[t]),t++;t++,n.push(L(u.join(`
|
|
4
|
+
`),c));continue}let o=s.match(/^(#{1,3})\s+(.+)$/);if(o){let c=Math.min(o[1].length,3);n.push(g(c,o[2].trim())),t++;continue}if(/^[-*]\s/.test(s)){let c=[];for(;t<e.length&&/^[-*]\s/.test(e[t]);){let u=e[t].replace(/^[-*]\s+/,"");c.push({type:"listItem",content:[{type:"paragraph",content:A(u)}]}),t++}n.push({type:"bulletList",content:c});continue}if(/^\d+\.\s/.test(s)){let c=[];for(;t<e.length&&/^\d+\.\s/.test(e[t]);){let u=e[t].replace(/^\d+\.\s+/,"");c.push({type:"listItem",content:[{type:"paragraph",content:A(u)}]}),t++}n.push({type:"orderedList",content:c});continue}if(s.trim()===""){t++;continue}let a=[];for(;t<e.length&&e[t].trim()!==""&&!/^(#{1,3}\s|[-*]\s|\d+\.\s|```)/.test(e[t]);)a.push(e[t]),t++;if(a.length>0){let c=[];for(let u=0;u<a.length;u++)c.push(...A(a[u])),u<a.length-1&&c.push({type:"hardBreak"});n.push({type:"paragraph",content:c})}}return n}function A(r){let e=[],n=/(\*\*(.+?)\*\*|\*(.+?)\*|`(.+?)`|_(.+?)_)/g,t=0,s;for(;(s=n.exec(r))!==null;)s.index>t&&e.push({type:"text",text:r.slice(t,s.index)}),s[2]!==void 0?e.push({type:"text",text:s[2],marks:[{type:"strong"}]}):s[3]!==void 0?e.push({type:"text",text:s[3],marks:[{type:"em"}]}):s[4]!==void 0?e.push({type:"text",text:s[4],marks:[{type:"code"}]}):s[5]!==void 0&&e.push({type:"text",text:s[5],marks:[{type:"em"}]}),t=s.index+s[0].length;return t<r.length&&e.push({type:"text",text:r.slice(t)}),e.length>0?e:[{type:"text",text:r}]}function L(r,e="text"){return{type:"codeBlock",attrs:{language:e},content:[{type:"text",text:r}]}}function ee(r){return{type:"bulletList",content:r.map(e=>({type:"listItem",content:[{type:"paragraph",content:[{type:"text",text:e}]}]}))}}function te(r){return{type:"bulletList",content:r.map(e=>({type:"listItem",content:[{type:"paragraph",content:[{type:"text",text:e,marks:[{type:"link",attrs:{href:e}}]}]}]}))}}function N(r,...e){return{type:"expand",attrs:{title:r},content:e}}function y(r){return{type:"paragraph",content:[{type:"text",text:r,marks:[{type:"em"}]}]}}function D(r){return!r||r.length===0?"":r.map(e=>E(e,"")).join(`
|
|
5
5
|
|
|
6
6
|
`)}function re(r,e){if(e.size===0)return"";let n=[`# Verwendungsb\xE4ume f\xFCr ${r}`,""];for(let[t,s]of[...e.entries()].sort(([i],[o])=>i.localeCompare(o))){n.push(`## ${t}`,"");let i=D(s);i&&n.push("```",i,"```"),n.push("")}return n.join(`
|
|
7
|
-
`)}function ne(r,e,n=new Map){let t=[],i=0;if(t.push(g(1,"Informationen zur Schwachstelle")),t.push(y("Diese Informationen wurden automatisch generiert.")),r.refs&&r.refs.length>0&&t.push(te(r.refs)),r.description){let o=Z(r.description);t.push(N("Beschreibung",...o))}t.push(g(2,"Betroffene Artefakte")),r.components&&r.components.length>0?t.push(ee(r.components)):t.push(y("Keine Komponenten bekannt.")),t.push(g(2,"Gemeldet f\xFCr"));for(let o of e.slice().sort()){t.push({type:"paragraph",content:[{type:"text",text:o,marks:[{type:"strong"}]}]});let
|
|
7
|
+
`)}function ne(r,e,n=new Map){let t=[],i=0;if(t.push(g(1,"Informationen zur Schwachstelle")),t.push(y("Diese Informationen wurden automatisch generiert.")),r.refs&&r.refs.length>0&&t.push(te(r.refs)),r.description){let o=Z(r.description);t.push(N("Beschreibung",...o))}t.push(g(2,"Betroffene Artefakte")),r.components&&r.components.length>0?t.push(ee(r.components)):t.push(y("Keine Komponenten bekannt.")),t.push(g(2,"Gemeldet f\xFCr"));for(let o of e.slice().sort()){t.push({type:"paragraph",content:[{type:"text",text:o,marks:[{type:"strong"}]}]});let a=n.get(o);if(a&&a.length>0){let c=D(a);c&&(i+=c.length,i>2e4?t.push(N("Verwendungsbaum",y("Gesamtgr\xF6\xDFe der Verwendungsb\xE4ume \xFCberschreitet Limit. Siehe Issue-Attachments f\xFCr vollst\xE4ndige B\xE4ume."))):t.push(N("Verwendungsbaum",L(c))))}}return t.push(g(1,"Einwertung")),t.push(y("Einwertung des CVE in Bezug auf die gemeldeten Projekte/Produkte. Dies kann eine Bewertung der Schwere, der Auswirkungen und der Wahrscheinlichkeit eines Angriffs umfassen.")),t.push(g(1,"Betroffen sind")),t.push(y("Abschlie\xDFende Darstellung, welche Projekte/Produkte tats\xE4chlich betroffen oder nicht betroffen sind.")),t.push(g(1,"Ma\xDFnahmen")),t.push(y("\xDCbersicht der zu ergreifenden Ma\xDFnahmen, um den CVE zu beheben oder zu mitigieren. Dies kann Patches, Updates oder Konfigurations\xE4nderungen umfassen.")),Y(...t)}var I=class{authHeader;baseUrl;constructor(e,n,t){this.baseUrl=e.replace(/\/$/,""),this.authHeader=`Basic ${Buffer.from(`${n}:${t}`).toString("base64")}`}get headers(){return{Authorization:this.authHeader,"Content-Type":"application/json",Accept:"application/json"}}async safeJson(e,n){let t=await e.text();try{return JSON.parse(t)}catch{let s=t.slice(0,500).replace(/\s+/g," ").trim();throw new Error(`${n}: expected JSON but got (HTTP ${e.status}): ${s}`)}}async issueExists(e,n){let t=n.replace(/"/g,'\\"'),s=`project = "${e}" AND summary ~ "\\"${t}\\""`,i=`${this.baseUrl}/rest/api/3/search/jql?jql=${encodeURIComponent(s)}&maxResults=1&fields=summary`,o=await fetch(i,{method:"GET",headers:this.headers});if(!o.ok){let c=await o.text();throw new Error(`Jira search failed (${o.status}): ${c}`)}return(await this.safeJson(o,"Jira search")).issues[0]?.key??null}async createIssue(e){let n={project:{key:e.projectKey},issuetype:{name:e.issueType},summary:e.summary,description:e.description};e.labels&&e.labels.length>0&&(n.labels=e.labels),e.sprintId!=null&&(n.customfield_10020=e.sprintId);let t={fields:n},s=await fetch(`${this.baseUrl}/rest/api/3/issue`,{method:"POST",headers:this.headers,body:JSON.stringify(t)});if(!s.ok){let o=await s.text();throw new Error(`Jira create issue failed (${s.status}): ${o}`)}return(await this.safeJson(s,"Jira create issue")).key}async attachMarkdown(e,n,t){let s=`${this.baseUrl}/rest/api/3/issue/${e}/attachments`,i=new FormData;i.append("file",new Blob([t],{type:"text/markdown"}),n);let o=await fetch(s,{method:"POST",headers:{Authorization:this.authHeader,"X-Atlassian-Token":"no-check"},body:i});if(!o.ok){let a=await o.text();throw new Error(`Jira attach file failed (${o.status}): ${a}`)}}async getActiveSprintId(e){let n=`${this.baseUrl}/rest/agile/1.0/board/${e}/sprint?state=active&maxResults=1`,t=await fetch(n,{method:"GET",headers:this.headers});if(!t.ok){let i=await t.text();throw new Error(`Jira agile sprint lookup failed (${t.status}): ${i}`)}return(await this.safeJson(t,"Jira sprint lookup")).values[0]?.id??null}};V.dotenv.config();var F=process.env;function se(r){let e=r.slice(2),n=process.cwd(),t=(0,J.join)(n,"jira-conf.json"),s=(0,J.join)(n,"output","scan-summary.json"),i=!1;for(let o=0;o<e.length;o++){let a=e[o];(a==="--jira-config"||a==="-c")&&e[o+1]?t=e[++o]:(a==="--summary"||a==="-s")&&e[o+1]?s=e[++o]:a==="--dry-run"?i=!0:a==="--help"||a==="-h"?(z(),process.exit(0)):(console.error(`Unknown argument: ${a}`),z(),process.exit(1))}return{jiraConfigPath:t,scanSummaryPath:s,dryRun:i}}function z(){console.log(`Usage: ct-vuln-jira-report [options]
|
|
8
8
|
|
|
9
9
|
Options:
|
|
10
10
|
-c, --jira-config <path> Path to jira-conf.json configuration file
|