@conterra/vuln-scan 0.0.36 → 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.
- package/README.md +38 -41
- package/dist/jira-reporter.js +7 -7
- package/dist/schema/jira-config-schema.json +32 -9
- package/dist/schema/summary-schema.json +12 -8
- package/dist/vuln-scan.js +156 -156
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -511,9 +511,9 @@ JIRA_API_TOKEN=<token> # Jira API token (https://id.atlassian.com/manage-p
|
|
|
511
511
|
|
|
512
512
|
NOTE: These variables can also be placed in a `.env` file in the working directory.
|
|
513
513
|
|
|
514
|
-
### Configuration – `jira.json`
|
|
514
|
+
### Configuration – `jira-conf.json`
|
|
515
515
|
|
|
516
|
-
Create a `jira.json` file (path is configurable) that defines the Jira instance and the routing rules:
|
|
516
|
+
Create a `jira-conf.json` file (path is configurable) that defines the Jira instance and the routing rules:
|
|
517
517
|
|
|
518
518
|
```json
|
|
519
519
|
{
|
|
@@ -523,9 +523,18 @@ Create a `jira.json` file (path is configurable) that defines the Jira instance
|
|
|
523
523
|
{
|
|
524
524
|
"jiraProject": "PLATFORM",
|
|
525
525
|
"issueType": "CVE",
|
|
526
|
-
"issueLabels": ["security"
|
|
527
|
-
"
|
|
528
|
-
|
|
526
|
+
"issueLabels": ["security"],
|
|
527
|
+
"addToBoard": [
|
|
528
|
+
{
|
|
529
|
+
"sprintBoardId": 10,
|
|
530
|
+
"issueLabels": ["team-a"],
|
|
531
|
+
"matchProjects": ["mapapps@*"]
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"issueLabels": ["team-b"],
|
|
535
|
+
"matchProjects": ["smartfinder@*"]
|
|
536
|
+
}
|
|
537
|
+
],
|
|
529
538
|
"reportProjects": [
|
|
530
539
|
"mapapps@*",
|
|
531
540
|
"smartfinder@4.*"
|
|
@@ -540,18 +549,27 @@ Create a `jira.json` file (path is configurable) that defines the Jira instance
|
|
|
540
549
|
}
|
|
541
550
|
```
|
|
542
551
|
|
|
543
|
-
| Field
|
|
544
|
-
|
|
|
545
|
-
| `jiraUrl`
|
|
546
|
-
| `issues`
|
|
547
|
-
| `issues[].jiraProject`
|
|
548
|
-
| `issues[].issueType`
|
|
549
|
-
| `issues[].reportProjects`
|
|
550
|
-
| `issues[].issueLabels`
|
|
551
|
-
| `issues[].
|
|
552
|
-
| `
|
|
552
|
+
| Field | Required | Description |
|
|
553
|
+
| ---------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
554
|
+
| `jiraUrl` | ✓ | Base URL of the Jira instance |
|
|
555
|
+
| `issues` | ✓ | List of routing rules |
|
|
556
|
+
| `issues[].jiraProject` | ✓ | Jira project key, e.g. `PLATFORM` |
|
|
557
|
+
| `issues[].issueType` | ✓ | Jira issue type name, e.g. `Bug`, `CVE` |
|
|
558
|
+
| `issues[].reportProjects` | ✓ | Glob patterns (`*` wildcard) for scan project identifiers (`name@version`) |
|
|
559
|
+
| `issues[].issueLabels` | – | Global labels attached to every issue created by this rule, merged with any labels from `addToBoard` entries |
|
|
560
|
+
| `issues[].addToBoard` | – | Per-board routing rules (see table below). Enables Scrum sprint assignment and/or Kanban label routing |
|
|
561
|
+
| `addToBoard[].sprintBoardId` | – | Scrum board ID. When set, the issue is assigned to the currently active sprint of that board via `customfield_10020`. Omit for Kanban boards. |
|
|
562
|
+
| `addToBoard[].issueLabels` | – | Labels contributed by this board entry (see routing rules below) |
|
|
563
|
+
| `addToBoard[].matchProjects` | – | Glob patterns to activate this entry. Defaults to `["*"]` (matches any project) |
|
|
564
|
+
|
|
565
|
+
**`addToBoard` routing rules:**
|
|
553
566
|
|
|
554
|
-
|
|
567
|
+
- Each entry in `addToBoard` is checked: its `matchProjects` patterns are tested against the scan projects affected by the current vulnerability. If none match, the entry is skipped.
|
|
568
|
+
- **Scrum (sprintBoardId set):** The **first** matching entry with a `sprintBoardId` claims the sprint. All subsequent matching sprint entries – including their labels – are ignored. This avoids ambiguous multi-sprint assignments, which Jira does not support.
|
|
569
|
+
- **Kanban (no sprintBoardId):** Matching entries always contribute their labels. This allows issues to appear on Kanban boards that filter by label.
|
|
570
|
+
- The final label set on the created issue is: `issueLabels` (top-level) + labels from the winning sprint entry (if any) + labels from all matching Kanban entries.
|
|
571
|
+
|
|
572
|
+
**`reportProjects` routing:** The `reportProjects` patterns are matched against each scan project identifier (`name@version`). The `*` wildcard matches any sequence of characters. Each routing rule is evaluated independently, so a vulnerability can be reported to multiple Jira projects. Rules that match no project are silently skipped.
|
|
555
573
|
|
|
556
574
|
### CLI Usage
|
|
557
575
|
|
|
@@ -561,8 +579,8 @@ $ ct-vuln-jira-report [options]
|
|
|
561
579
|
|
|
562
580
|
```
|
|
563
581
|
Options:
|
|
564
|
-
-c, --jira-config <path> Path to jira.json configuration file
|
|
565
|
-
(default: ./jira.json)
|
|
582
|
+
-c, --jira-config <path> Path to jira-conf.json configuration file
|
|
583
|
+
(default: ./jira-conf.json)
|
|
566
584
|
-s, --summary <path> Path to scan-summary.json produced by ct-vuln-scan
|
|
567
585
|
(default: ./output/scan-summary.json)
|
|
568
586
|
--dry-run Print what would be created without calling Jira
|
|
@@ -572,37 +590,16 @@ Options:
|
|
|
572
590
|
Examples:
|
|
573
591
|
|
|
574
592
|
```sh
|
|
575
|
-
# Use defaults (./jira.json + ./output/scan-summary.json)
|
|
593
|
+
# Use defaults (./jira-conf.json + ./output/scan-summary.json)
|
|
576
594
|
$ ct-vuln-jira-report
|
|
577
595
|
|
|
578
596
|
# Custom paths
|
|
579
|
-
$ ct-vuln-jira-report --jira-config ./config/jira.json --summary ./output/scan-summary.json
|
|
597
|
+
$ ct-vuln-jira-report --jira-config ./config/jira-conf.json --summary ./output/scan-summary.json
|
|
580
598
|
|
|
581
599
|
# Preview without creating issues
|
|
582
600
|
$ ct-vuln-jira-report --dry-run
|
|
583
601
|
```
|
|
584
602
|
|
|
585
|
-
### Issue Title Format
|
|
586
|
-
|
|
587
|
-
```
|
|
588
|
-
[<SEVERITY>] <Vuln-Id>: <Vuln-Title>
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
Example: `[HIGH] CVE-2024-0001: Remote code execution in foo-lib`
|
|
592
|
-
|
|
593
|
-
### Issue Description Structure
|
|
594
|
-
|
|
595
|
-
Each created issue contains the following sections (in Jira ADF format):
|
|
596
|
-
|
|
597
|
-
| Section | Content |
|
|
598
|
-
| ------------------------ | --------------------------------------------------------- |
|
|
599
|
-
| **CVE Info** | Vulnerability description and dependency tree (ASCII art) |
|
|
600
|
-
| **Betroffene Artefakte** | List of affected component purls |
|
|
601
|
-
| **Gemeldete Produkte** | Scan projects matched by this routing rule |
|
|
602
|
-
| **Einwertung** | Placeholder for severity/impact assessment |
|
|
603
|
-
| **Betroffene Produkte** | Placeholder for final affected/not-affected determination |
|
|
604
|
-
| **Maßnahmen** | Placeholder for remediation steps |
|
|
605
|
-
|
|
606
603
|
## Dev Notes
|
|
607
604
|
|
|
608
605
|
Setup:
|
package/dist/jira-reporter.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var
|
|
3
|
-
`,s+=o)}return s}async function
|
|
4
|
-
`),a));continue}let o=s.match(/^(#{1,3})\s+(.+)$/);if(o){let a=Math.min(o[1].length,3);
|
|
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
5
|
|
|
6
|
-
`)}function
|
|
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
7
|
|
|
8
8
|
Options:
|
|
9
|
-
-c, --jira-config <path> Path to jira.json configuration file
|
|
10
|
-
(default: ./jira.json)
|
|
9
|
+
-c, --jira-config <path> Path to jira-conf.json configuration file
|
|
10
|
+
(default: ./jira-conf.json)
|
|
11
11
|
-s, --summary <path> Path to scan-summary.json produced by ct-vuln-scan
|
|
12
12
|
(default: ./output/scan-summary.json)
|
|
13
13
|
--dry-run Print what would be created without calling Jira
|
|
@@ -16,4 +16,4 @@ Options:
|
|
|
16
16
|
Required environment variables:
|
|
17
17
|
JIRA_USER Jira account e-mail address
|
|
18
18
|
JIRA_API_TOKEN Jira API token (see https://id.atlassian.com/manage-profile/security/api-tokens)
|
|
19
|
-
`)}(async()=>{let{jiraConfigPath:
|
|
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)}})();
|
|
@@ -41,20 +41,18 @@
|
|
|
41
41
|
"type": "string"
|
|
42
42
|
},
|
|
43
43
|
"issueLabels": {
|
|
44
|
-
"description": "
|
|
44
|
+
"description": "Global Jira labels applied to every issue created by this rule, regardless of addToBoard matching. Merged with any labels from matching addToBoard entries. Example: [\"security\", \"cve\"].",
|
|
45
45
|
"type": "array",
|
|
46
46
|
"items": {
|
|
47
47
|
"type": "string"
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
|
-
"
|
|
51
|
-
"description": "
|
|
52
|
-
"type": "
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"type": "boolean",
|
|
57
|
-
"default": false
|
|
50
|
+
"addToBoard": {
|
|
51
|
+
"description": "Per-board routing rules. Each entry can target a Scrum sprint board (via sprintBoardId) and/or add labels for Kanban board routing. Sprint assignment: the first matching entry with a sprintBoardId wins; subsequent matching sprint entries and their labels are ignored. Kanban entries (no sprintBoardId) always contribute their labels.",
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": {
|
|
54
|
+
"$ref": "#/$defs/BoardConfig"
|
|
55
|
+
}
|
|
58
56
|
},
|
|
59
57
|
"reportProjects": {
|
|
60
58
|
"description": "Glob patterns for project identifiers (name@version) whose vulnerabilities should be routed to this Jira project. Use \"*\" to match any string. Examples: \"mapapps@*\", \"smartfinder@4.*\", \"*\".",
|
|
@@ -66,6 +64,31 @@
|
|
|
66
64
|
}
|
|
67
65
|
}
|
|
68
66
|
}
|
|
67
|
+
},
|
|
68
|
+
"BoardConfig": {
|
|
69
|
+
"description": "Per-board routing rule within addToBoard. Controls which projects activate this entry, whether to assign the issue to an active Scrum sprint, and which labels to attach.",
|
|
70
|
+
"type": "object",
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"properties": {
|
|
73
|
+
"sprintBoardId": {
|
|
74
|
+
"description": "Jira Scrum board ID. When set, the issue is assigned to the currently active sprint of that board. Omit for Kanban boards (routing is handled by Jira's board JQL filter).",
|
|
75
|
+
"type": "integer"
|
|
76
|
+
},
|
|
77
|
+
"issueLabels": {
|
|
78
|
+
"description": "Labels to attach when this entry matches. For sprint entries: included only if this is the first matching sprint entry. For Kanban entries (no sprintBoardId): always aggregated.",
|
|
79
|
+
"type": "array",
|
|
80
|
+
"items": {
|
|
81
|
+
"type": "string"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"matchProjects": {
|
|
85
|
+
"description": "Glob patterns for project identifiers (name@version) that activate this board entry. Defaults to [\"*\"] when omitted (matches any project). Example: [\"mapapps@*\", \"smartfinder@4.*\"].",
|
|
86
|
+
"type": "array",
|
|
87
|
+
"items": {
|
|
88
|
+
"type": "string"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
69
92
|
}
|
|
70
93
|
}
|
|
71
94
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"vulnerabilities",
|
|
10
10
|
"vulnerabilityToProject",
|
|
11
11
|
"projectToVulnerability",
|
|
12
|
+
"projectsToComponents",
|
|
12
13
|
"noLongerDetectedVulnerabilities"
|
|
13
14
|
],
|
|
14
15
|
"additionalProperties": false,
|
|
@@ -51,6 +52,16 @@
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
},
|
|
55
|
+
"projectsToComponents": {
|
|
56
|
+
"description": "Map of project identifier (name@version) to a map of component purl to its reverse dependency tree node. Contains the component usage trees for all vulnerable components per project.",
|
|
57
|
+
"type": "object",
|
|
58
|
+
"additionalProperties": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"additionalProperties": {
|
|
61
|
+
"$ref": "#/$defs/ComponentNode"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
54
65
|
"noLongerDetectedVulnerabilities": {
|
|
55
66
|
"description": "Map of CVE/vulnerability ID to the list of project identifiers (name@version) where the vulnerability was declared but is no longer detected.",
|
|
56
67
|
"type": "object",
|
|
@@ -90,7 +101,7 @@
|
|
|
90
101
|
"ShortVulnInfo": {
|
|
91
102
|
"description": "Condensed vulnerability information for a single CVE/vulnerability.",
|
|
92
103
|
"type": "object",
|
|
93
|
-
"required": ["id", "severity", "title", "description", "components", "
|
|
104
|
+
"required": ["id", "severity", "title", "description", "components", "refs"],
|
|
94
105
|
"additionalProperties": false,
|
|
95
106
|
"properties": {
|
|
96
107
|
"id": {
|
|
@@ -115,13 +126,6 @@
|
|
|
115
126
|
"type": "string"
|
|
116
127
|
}
|
|
117
128
|
},
|
|
118
|
-
"componentTree": {
|
|
119
|
-
"description": "Reverse dependency tree nodes showing how affected components are used.",
|
|
120
|
-
"type": "array",
|
|
121
|
-
"items": {
|
|
122
|
-
"$ref": "#/$defs/ComponentNode"
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
129
|
"refs": {
|
|
126
130
|
"description": "List of reference URLs (advisories, NVD entries, etc.).",
|
|
127
131
|
"type": "array",
|