@conterra/vuln-scan 0.0.35 → 0.1.0

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.
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ "use strict";var A=require("path"),K=require("zx");var N=require("fs/promises");function E(r,e=""){return B(r,e)}function B(r,e="",n=new Set){if(n.has(r.purl))return`${e}- ${r.purl} (cycle detected)`;n.add(r.purl);let t=n.size===1,s=`${e}- ${r.purl}`;for(let i of r.usedBy){let o=B(i,e+(t?" ":" "),new Set(n));o!==""&&(s+=`
3
+ `,s+=o)}return s}async function M(r){let{scanSummaryPath:e,jiraConfigPath:n,jiraUser:t,jiraApiToken:s,dryRun:i=!1}=r,[o,c]=await Promise.all([H(e),W(n)]),a={created:[],skipped:[],errors:[]},u=new k(c.jiraUrl,t,s),x=new Map;for(let[g,P]of Object.entries(o.vulnerabilities)){let V=o.vulnerabilityToProject[g]??[];for(let p of c.issues){let h=V.filter(m=>O(m,p.reportProjects));if(h.length===0)continue;let $=Q(P);try{let m=await u.issueExists(p.jiraProject,g);if(m){console.log(`[skip] Issue for ${g} already exists: ${m}`),a.skipped.push(`${p.jiraProject}::${g}`);continue}if(i){console.log(`[dry-run] Would create issue in ${p.jiraProject}: "${$}"`),console.log(` Matching projects: ${h.join(", ")}`),a.created.push(`${p.jiraProject}::${g}`);continue}let y,C=[],I=!1;for(let l of p.addToBoard??[]){let T=l.matchProjects??["*"];if(h.some(d=>O(d,T)))if(l.sprintBoardId!=null){if(!I){I=!0;let d=l.sprintBoardId;if(!x.has(d)){let j=await u.getActiveSprintId(d).catch(z=>(console.warn(`[warn] Could not resolve active sprint for boardId ${d}: ${String(z)}`),null));x.set(d,j)}y=x.get(d)??void 0,l.issueLabels&&C.push(...l.issueLabels)}}else l.issueLabels&&C.push(...l.issueLabels)}let R=[...p.issueLabels??[],...C],J=new Map;if(o.projectsToComponents)for(let l of h){let T=o.projectsToComponents[l]??{},w=[];for(let d of P.components){let j=T[d];j&&w.push(j)}w.length>0&&J.set(l,w)}let _=re(P,h,J),v=await u.createIssue({projectKey:p.jiraProject,issueType:p.issueType,summary:$,description:_,labels:R.length>0?R:void 0,sprintId:y});console.log(`[created] ${v} in ${p.jiraProject}: "${$}"`),a.created.push(v)}catch(m){let y=`Failed to process ${g} for project ${p.jiraProject}: ${String(m)}`;console.error(`[error] ${y}`),a.errors.push(y)}}}return a}async function H(r){let e=await(0,N.readFile)(r,"utf8");return JSON.parse(e)}async function W(r){let e=await(0,N.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 q(r){return r.replace(/[.+?^${}()|[\]\\]/g,"\\$&")}function G(r,e){let n=e.split("*").map(q).join(".*");return new RegExp(`^${n}$`).test(r)}function O(r,e){return e.some(n=>G(r,n))}function Q(r){let e=r.title?`: ${r.title}`:"";return`[${r.severity}] ${r.id}${e}`}function X(...r){return{type:"doc",version:1,content:r}}function f(r,e){return{type:"heading",attrs:{level:r},content:[{type:"text",text:e}]}}function Y(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 a=i[1]??"text",u=[];for(t++;t<e.length&&!e[t].startsWith("```");)u.push(e[t]),t++;t++,n.push(L(u.join(`
4
+ `),a));continue}let o=s.match(/^(#{1,3})\s+(.+)$/);if(o){let a=Math.min(o[1].length,3);n.push(f(a,o[2].trim())),t++;continue}if(/^[-*]\s/.test(s)){let a=[];for(;t<e.length&&/^[-*]\s/.test(e[t]);){let u=e[t].replace(/^[-*]\s+/,"");a.push({type:"listItem",content:[{type:"paragraph",content:S(u)}]}),t++}n.push({type:"bulletList",content:a});continue}if(/^\d+\.\s/.test(s)){let a=[];for(;t<e.length&&/^\d+\.\s/.test(e[t]);){let u=e[t].replace(/^\d+\.\s+/,"");a.push({type:"listItem",content:[{type:"paragraph",content:S(u)}]}),t++}n.push({type:"orderedList",content:a});continue}if(s.trim()===""){t++;continue}let c=[];for(;t<e.length&&e[t].trim()!==""&&!/^(#{1,3}\s|[-*]\s|\d+\.\s|```)/.test(e[t]);)c.push(e[t]),t++;if(c.length>0){let a=[];for(let u=0;u<c.length;u++)a.push(...S(c[u])),u<c.length-1&&a.push({type:"hardBreak"});n.push({type:"paragraph",content:a})}}return n}function S(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 Z(r){return{type:"bulletList",content:r.map(e=>({type:"listItem",content:[{type:"paragraph",content:[{type:"text",text:e}]}]}))}}function ee(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 U(r,...e){return{type:"expand",attrs:{title:r},content:e}}function b(r){return{type:"paragraph",content:[{type:"text",text:r,marks:[{type:"em"}]}]}}function te(r){return!r||r.length===0?"":r.map(e=>E(e,"")).join(`
5
+
6
+ `)}function re(r,e,n=new Map){let t=[];if(t.push(f(1,"CVE Info (automatisch generiert)")),r.refs&&r.refs.length>0&&t.push(ee(r.refs)),r.description){let s=Y(r.description);t.push(U("Beschreibung",...s))}t.push(f(2,"Betroffene Artefakte")),r.components&&r.components.length>0?t.push(Z(r.components)):t.push(b("Keine Komponenten bekannt.")),t.push(f(2,"Gemeldet f\xFCr"));for(let s of e.slice().sort()){t.push({type:"paragraph",content:[{type:"text",text:s,marks:[{type:"strong"}]}]});let i=n.get(s);if(i&&i.length>0){let o=te(i);o&&t.push(U("Verwendungsbaum",L(o)))}}return t.push(f(2,"Einwertung")),t.push(b("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(f(2,"Betroffen sind")),t.push(b("Abschlie\xDFende Darstellung, welche Projekte/Produkte tats\xE4chlich betroffen oder nicht betroffen sind.")),t.push(f(2,"Ma\xDFnahmen")),t.push(b("\xDCbersicht der zu ergreifenden Ma\xDFnahmen, um den CVE zu beheben oder zu mitigieren. Dies kann Patches, Updates oder Konfigurations\xE4nderungen umfassen.")),X(...t)}var k=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 a=await o.text();throw new Error(`Jira search failed (${o.status}): ${a}`)}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 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}};K.dotenv.config();var F=process.env;function ne(r){let e=r.slice(2),n=process.cwd(),t=(0,A.join)(n,"jira-conf.json"),s=(0,A.join)(n,"output","scan-summary.json"),i=!1;for(let o=0;o<e.length;o++){let c=e[o];(c==="--jira-config"||c==="-c")&&e[o+1]?t=e[++o]:(c==="--summary"||c==="-s")&&e[o+1]?s=e[++o]:c==="--dry-run"?i=!0:c==="--help"||c==="-h"?(D(),process.exit(0)):(console.error(`Unknown argument: ${c}`),D(),process.exit(1))}return{jiraConfigPath:t,scanSummaryPath:s,dryRun:i}}function D(){console.log(`Usage: ct-vuln-jira-report [options]
7
+
8
+ Options:
9
+ -c, --jira-config <path> Path to jira-conf.json configuration file
10
+ (default: ./jira-conf.json)
11
+ -s, --summary <path> Path to scan-summary.json produced by ct-vuln-scan
12
+ (default: ./output/scan-summary.json)
13
+ --dry-run Print what would be created without calling Jira
14
+ -h, --help Show this help message
15
+
16
+ Required environment variables:
17
+ JIRA_USER Jira account e-mail address
18
+ JIRA_API_TOKEN Jira API token (see https://id.atlassian.com/manage-profile/security/api-tokens)
19
+ `)}(async()=>{let{jiraConfigPath:r,scanSummaryPath:e,dryRun:n}=ne(process.argv),t=F.JIRA_USER,s=F.JIRA_API_TOKEN;(!t||!s)&&(console.error("Error: JIRA_USER and JIRA_API_TOKEN environment variables must be set."),process.exit(1)),console.log(`Jira reporter${n?" [dry-run]":""}`),console.log(` config: ${r}`),console.log(` summary: ${e}`),console.log("");try{let i=await M({scanSummaryPath:e,jiraConfigPath:r,jiraUser:t,jiraApiToken:s,dryRun:n});console.log(""),console.log("=== Jira Report Summary ==="),console.log(` Created : ${i.created.length}`),console.log(` Skipped : ${i.skipped.length}`),console.log(` Errors : ${i.errors.length}`),i.errors.length>0&&(process.exitCode=1)}catch(i){console.error(`Fatal error: ${String(i)}`),process.exit(1)}})();