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