-
Notifications
You must be signed in to change notification settings - Fork 334
feat: CVE conversion triager tool #4916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jess-lowe
merged 22 commits into
google:master
from
jess-lowe:cve-conversion-triager-tool
Mar 5, 2026
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
cb00d98
feat: Add CVE conversion triage page
google-labs-jules[bot] 623ea94
feat: Add CVE conversion triage page
google-labs-jules[bot] 8025f2d
feat: Add CVE conversion triage page
google-labs-jules[bot] db6c422
feat: Add CVE conversion triage page
google-labs-jules[bot] 95d87b5
Merge pull request #5 from jess-lowe/cve-conversion-triager-109754468…
jess-lowe 48a23bd
Progress
jess-lowe 2cf4f71
fix using path proxy and instead use source and id
jess-lowe 82c8f83
fix overflow problem
jess-lowe 1f46199
put title and vuln search inline
jess-lowe f95adc1
Merge pull request #6 from jess-lowe/cve-conversion-triager-109754468…
jess-lowe 71067df
Update gcp/website/frontend3/src/templates/triage.html
jess-lowe 49378ff
Update gcp/website/frontend3/src/templates/triage.html
jess-lowe f688808
Update gcp/website/frontend3/src/triage.scss
jess-lowe 2560c7a
Update gcp/website/frontend3/src/triage.js
jess-lowe 2e752b9
Update gcp/website/frontend3/src/triage.js
jess-lowe 2fac31f
address nits
jess-lowe 968d549
some improvements to reallly hopefully prevent xss
jess-lowe 5af65c3
fix lint
jess-lowe 231e3dd
Make sure this is only run locally for now
jess-lowe 05a086d
lint
jess-lowe 5d21790
Apply suggestion from @another-rex
jess-lowe df60885
bring back capture groups
jess-lowe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| {% extends 'base.html' %} | ||
| {% set active_section = 'triage' %} | ||
|
|
||
| {% block content %} | ||
| <div class="triage-container"> | ||
| <div class="header-container"> | ||
| <h1>CVE Conversion Triager</h1> | ||
|
|
||
| <div class="input-section"> | ||
| <md-filled-text-field id="vuln-id-input" label="Vulnerability ID" placeholder="e.g. CVE-2023-1234" class="standard-input"></md-filled-text-field> | ||
| <md-filled-button id="load-btn">Load</md-filled-button> | ||
| </div> | ||
| </div> | ||
| <div class="triage-grid"> | ||
| {% for i in range(1, 4) %} | ||
| <div class="triage-column" id="column-{{ i }}"> | ||
| <div class="column-header"> | ||
| <label for="source-select-{{ i }}">Source:</label> | ||
| <div class="select-wrapper"> | ||
| <select id="source-select-{{ i }}" class="source-select"> | ||
| <option value="">Select Source...</option> | ||
| <optgroup label="External APIs"> | ||
| <option value="cve-org">CVE.org API</option> | ||
| <option value="nvd-api">NVD API 2.0</option> | ||
| </optgroup> | ||
| <optgroup label="Test Instance (gs://osv-test-cve-osv-conversion)"> | ||
| <option value="test-nvd">NVD-OSV (JSON)</option> | ||
| <option value="test-cve5">CVE5 (JSON)</option> | ||
| <option value="test-osv">OSV Output (JSON)</option> | ||
| <option value="test-nvd-metrics">NVD Metrics</option> | ||
| <option value="test-cve5-metrics">CVE5 Metrics</option> | ||
| </optgroup> | ||
| <optgroup label="Prod Instance (gs://cve-osv-conversion)"> | ||
| <option value="prod-nvd">NVD-OSV (JSON)</option> | ||
| <option value="prod-cve5">CVE5 (JSON)</option> | ||
| <option value="prod-osv">OSV Output (JSON)</option> | ||
| <option value="prod-nvd-metrics">NVD Metrics</option> | ||
| <option value="prod-cve5-metrics">CVE5 Metrics</option> | ||
| </optgroup> | ||
| <optgroup label="API"> | ||
| <option value="api-test">api.test.osv.dev</option> | ||
| <option value="api-prod">api.osv.dev</option> | ||
| </optgroup> | ||
| </select> | ||
| </div> | ||
| </div> | ||
| <div class="content-display"> | ||
| <div class="loading-spinner hidden"> | ||
| <md-circular-progress indeterminate></md-circular-progress> | ||
| </div> | ||
| <pre class="json-content">Select a source to view content</pre> | ||
| </div> | ||
| </div> | ||
| {% endfor %} | ||
| </div> | ||
| </div> | ||
| {% endblock %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| import "./triage.scss"; | ||
| import "@material/web/textfield/filled-text-field.js"; | ||
| import "@material/web/button/filled-button.js"; | ||
| import "@material/web/progress/circular-progress.js"; | ||
|
|
||
| document.addEventListener("DOMContentLoaded", () => { | ||
| const vulnIdInput = document.getElementById("vuln-id-input"); | ||
| const loadBtn = document.getElementById("load-btn"); | ||
| const columns = document.querySelectorAll(".triage-column"); | ||
|
|
||
| // Map selection values to their respective endpoints/paths | ||
| const sourceConfigMap = { | ||
| // External APIs | ||
| "cve-org": { | ||
| proxySource: "cve", | ||
| }, | ||
| "nvd-api": { | ||
| proxySource: "nvd", | ||
| }, | ||
| // Test Instance | ||
| "test-nvd": { | ||
| proxySource: "test-nvd", | ||
| }, | ||
| "test-cve5": { | ||
| proxySource: "test-cve5", | ||
| }, | ||
| "test-osv": { | ||
| proxySource: "test-osv", | ||
| }, | ||
| "test-nvd-metrics": { | ||
| proxySource: "test-nvd-metrics", | ||
| }, | ||
| "test-cve5-metrics": { | ||
| proxySource: "test-cve5-metrics", | ||
| }, | ||
| // Prod Instance | ||
| "prod-nvd": { | ||
| proxySource: "prod-nvd", | ||
| }, | ||
| "prod-cve5": { | ||
| proxySource: "prod-cve5", | ||
| }, | ||
| "prod-osv": { | ||
| proxySource: "prod-osv", | ||
| }, | ||
| "prod-nvd-metrics": { | ||
| proxySource: "prod-nvd-metrics", | ||
| }, | ||
| "prod-cve5-metrics": { | ||
| proxySource: "prod-cve5-metrics", | ||
| }, | ||
| // API | ||
| "api-test": { | ||
| urlTemplate: "https://api.test.osv.dev/v1/vulns/{id}", | ||
| }, | ||
| "api-prod": { | ||
| urlTemplate: "https://api.osv.dev/v1/vulns/{id}", | ||
| }, | ||
| }; | ||
|
|
||
| function escapeHtml(text) { | ||
| const div = document.createElement('div'); | ||
| div.textContent = text; | ||
| return div.innerHTML; | ||
| } | ||
|
|
||
| function syntaxHighlight(json) { // credit to https://codepen.io/absolutedevelopment/pen/EpwVzN | ||
| if (typeof json !== 'string') { | ||
| json = JSON.stringify(json, undefined, 2); | ||
| } | ||
|
|
||
| const escapedJson = escapeHtml(json); | ||
|
|
||
| return escapedJson.replace( | ||
| /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, | ||
| function (match) { | ||
| let cls = 'json-number'; | ||
| if (/^"/.test(match)) { | ||
| if (/:$/.test(match)) { | ||
| cls = 'json-key'; | ||
| } else { | ||
| cls = 'json-string'; | ||
| } | ||
| } else if (/true|false/.test(match)) { | ||
| cls = 'json-boolean'; | ||
| } else if (/null/.test(match)) { | ||
| cls = 'json-null'; | ||
| } | ||
| return `<span class="${cls}">${match}</span>`; | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| async function fetchData(sourceKey, vulnId) { | ||
| const config = sourceConfigMap[sourceKey]; | ||
| let url; | ||
|
|
||
| const safeId = encodeURIComponent(vulnId); | ||
|
|
||
| if (config.proxySource) { | ||
| url = `/triage/proxy?source=${encodeURIComponent(config.proxySource)}&id=${safeId}`; | ||
| } else if (config.urlTemplate) { | ||
| url = config.urlTemplate.replace("{id}", safeId); | ||
| } else { | ||
| throw new Error("Invalid configuration"); | ||
| } | ||
|
|
||
| const response = await fetch(url); | ||
| if (!response.ok) { | ||
| throw new Error(response.status === 404 ? "Not Found" : `Error: ${response.statusText}`); | ||
| } | ||
| return response.json(); | ||
| } | ||
|
|
||
| function updateColumn(column) { | ||
| const select = column.querySelector(".source-select"); | ||
| const contentPre = column.querySelector(".json-content"); | ||
| const spinner = column.querySelector(".loading-spinner"); | ||
| const sourceKey = select.value; | ||
| const vulnId = vulnIdInput.value.trim(); | ||
|
|
||
| if (!sourceKey) { | ||
| contentPre.textContent = "Select a source to view content"; | ||
| return; | ||
| } | ||
|
|
||
| if (!vulnId) { | ||
| contentPre.textContent = "Please enter a Vulnerability ID"; | ||
| return; | ||
| } | ||
|
|
||
| spinner.classList.remove("hidden"); | ||
| contentPre.textContent = ""; | ||
|
|
||
| fetchData(sourceKey, vulnId) | ||
| .then((data) => { | ||
| contentPre.innerHTML = syntaxHighlight(data); | ||
| }) | ||
| .catch((error) => { | ||
| contentPre.textContent = error.message; | ||
| }) | ||
| .finally(() => { | ||
| spinner.classList.add("hidden"); | ||
| }); | ||
| } | ||
|
|
||
| loadBtn.addEventListener("click", () => { | ||
| columns.forEach((col) => updateColumn(col)); | ||
| }); | ||
|
|
||
| // Also handle Enter key on the input field | ||
| vulnIdInput.addEventListener("keydown", (e) => { | ||
| if (e.key === "Enter") { | ||
| columns.forEach((col) => updateColumn(col)); | ||
| } | ||
| }); | ||
|
|
||
| // Individual column updates when dropdown changes | ||
| columns.forEach((col) => { | ||
| const select = col.querySelector(".source-select"); | ||
| select.addEventListener("change", () => { | ||
| if (vulnIdInput.value.trim()) { | ||
| updateColumn(col); | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| .triage-container { | ||
| // padding: 40px; | ||
| padding: 20px 40px; | ||
| width: 100%; | ||
| box-sizing: border-box; | ||
|
|
||
| --md-sys-color-primary: #c5221f; | ||
| --md-sys-color-on-primary: #ffffff; | ||
| --md-filled-text-field-focus-active-indicator-color: #c5221f; | ||
| --md-filled-text-field-focus-label-text-color: #c5221f; | ||
| --md-filled-button-container-color: #c5221f; | ||
|
|
||
| .header-container { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| margin-bottom: 32px; | ||
| } | ||
| h1 { | ||
| margin: 0; | ||
| } | ||
|
|
||
| .input-section { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 16px; | ||
|
|
||
|
|
||
| .standard-input { | ||
| flex: 1; | ||
| max-width: 400px; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| .triage-grid { | ||
| display: grid; | ||
| grid-template-columns: repeat(3, 1fr); | ||
| gap: 24px; | ||
| } | ||
|
|
||
| .triage-column { | ||
| border: 1px solid #555; | ||
| border-radius: 8px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| height: 75vh; | ||
| background-color: #333; | ||
| } | ||
|
|
||
| .column-header { | ||
| padding: 16px; | ||
| background-color: #3c4043; | ||
| border-bottom: 1px solid #555; | ||
| border-top-left-radius: 8px; | ||
| border-top-right-radius: 8px; | ||
|
|
||
| label { | ||
| font-weight: bold; | ||
| margin-bottom: 8px; | ||
| display: block; | ||
| } | ||
| } | ||
|
|
||
| .select-wrapper { | ||
| margin-top: 5px; | ||
| } | ||
|
|
||
| .source-select { | ||
| width: 100%; | ||
| padding: 8px; | ||
| background-color: #292929; | ||
| color: #fff; | ||
| border: 1px solid #555; | ||
| border-radius: 4px; | ||
| } | ||
|
|
||
| .content-display { | ||
| flex-grow: 1; | ||
| padding: 16px; | ||
| overflow: auto; | ||
| position: relative; | ||
| background-color: #1e1e1e; | ||
| } | ||
|
|
||
| .json-content { | ||
| white-space: pre-wrap; | ||
| word-wrap: break-word; | ||
| font-family: "Overpass Mono", monospace; | ||
| font-size: 13px; | ||
| overflow-wrap: anywhere; | ||
| margin: 0; | ||
| color: #d4d4d4; | ||
|
|
||
| .json-key { | ||
| color: #9cdcfe; | ||
| } | ||
|
|
||
| .json-string { | ||
| color: #ce9178; | ||
| } | ||
|
|
||
| .json-number { | ||
| color: #b5cea8; | ||
| } | ||
|
|
||
| .json-boolean { | ||
| color: #569cd6; | ||
| } | ||
|
|
||
| .json-null { | ||
| color: #569cd6; | ||
| } | ||
| } | ||
|
|
||
| .loading-spinner { | ||
| position: absolute; | ||
| top: 50%; | ||
| left: 50%; | ||
| transform: translate(-50%, -50%); | ||
| --md-circular-progress-active-indicator-color: white; | ||
| } | ||
|
|
||
| .hidden { | ||
| display: none; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.