Last active
September 8, 2024 22:02
-
-
Save thoughtpolice/cff5016514057bde5c1a4cf15ad28661 to your computer and use it in GitHub Desktop.
Revisions
-
thoughtpolice revised this gist
Sep 8, 2024 . 1 changed file with 9 additions and 6 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -5,15 +5,12 @@ import { parse as parseTOML } from "jsr:@std/toml"; // --------------------------------------------------------------------------------------------------------------------- const OSV_API_BASE = "https://api.osv.dev/v1"; const QUERY_BATCH_SIZE = 30; // This is an entry in a [[package]] section of a Cargo.lock file type CargoLockPkg = { name: string; @@ -36,8 +33,14 @@ type OSVRustRequest = { }; }; // -- MARK: Cargo.lock parsing const rust_lock_path = Deno.args.length === 0 ? "buck/third-party/rust/Cargo.lock" : Deno.args[0]; const rust_lock = await Deno.readTextFile(rust_lock_path); console.log(`Examining Rust packages in ${rust_lock_path}...`); // deno-lint-ignore no-explicit-any const toml: Record<string, any> = parseTOML(rust_lock); -
thoughtpolice created this gist
Sep 8, 2024 .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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,214 @@ // SPDX-FileCopyrightText: © 2024 Austin Seipp // SPDX-License-Identifier: Apache-2.0 // 3p-osv-rust: check buck/third-party/rust/Cargo.lock against https://osv.dev metadata import { parse as parseTOML } from "jsr:@std/toml"; const OSV_API_BASE = "https://api.osv.dev/v1"; const QUERY_BATCH_SIZE = 30; // --------------------------------------------------------------------------------------------------------------------- // -- MARK: Cargo.lock parsing console.log("Examining Rust packages in Cargo.lock..."); // This is an entry in a [[package]] section of a Cargo.lock file type CargoLockPkg = { name: string; version: string; source: string; checksum: string; dependencies: string[]; features: string[]; optional: boolean; platform: string[]; uid: string; id: string; yanked: boolean; }; type OSVRustRequest = { version: string; package: { purl: string; }; }; const rust_lock_path = "buck/third-party/rust/Cargo.lock"; const rust_lock = await Deno.readTextFile(rust_lock_path); // deno-lint-ignore no-explicit-any const toml: Record<string, any> = parseTOML(rust_lock); if (!toml) { console.error("ERROR: Could not parse Cargo.lock file"); Deno.exit(1); } if (toml["version"] !== 3) { console.error("ERROR: Cargo.lock file is not version 3"); Deno.exit(1); } const packages: CargoLockPkg[] = []; // make sure we understand every top-level key/value pair for (const key in toml) { if (key === "version") continue; if (key === "package") { packages.push(...toml[key]); continue; } console.error(`ERROR: Unexpected key in Cargo.lock: ${key}`); Deno.exit(1); } // --------------------------------------------------------------------------------------------------------------------- // -- MARK: Request batching const num_pkgs = Object.keys(packages).length; const all_batches: OSVRustRequest[][] = []; // First, batch sets of Cargo packages into sizes of BATCH_SIZE, because it's // just not feasible to go through hundreds of packages in one go. let current_batch: OSVRustRequest[] = []; for (const pkg in packages) { const p = packages[pkg]; if (!p.name || !p.version) { console.error(`ERROR: Package is missing name or version: ${p}`); Deno.exit(1); } const body: OSVRustRequest = { version: p.version, package: { purl: `pkg:cargo/${p.name}` }, }; current_batch.push(body); if (Object.keys(current_batch).length >= QUERY_BATCH_SIZE) { all_batches.push(current_batch); current_batch = []; } } if (Object.keys(current_batch).length > 0) { all_batches.push(current_batch); current_batch = []; } // sanity check: ensure total sum of all batches is equal to the number of packages const sum = all_batches.reduce( (acc, batch) => acc + Object.keys(batch).length, 0, ); if (sum !== num_pkgs) { console.error( `ERROR: Sum of all batches (${sum}) does not equal number of packages (${num_pkgs})??? This is a bug!`, ); Deno.exit(1); } console.log( `Found ${num_pkgs} packages in Cargo.lock; split into ${all_batches.length} batches`, ); // --------------------------------------------------------------------------------------------------------------------- // -- MARK: Querying // now turn the batches into a set of promises const promises = all_batches.map(async (batch) => { const resp = await fetch(`${OSV_API_BASE}/querybatch`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ queries: batch }), }); const json = await resp.json(); return json["results"]; }); // now, use Promises.all to wait for all the batches to complete console.log("Querying OSV for Rust package vulnerabilities..."); const results = await Promise.all(promises); // --------------------------------------------------------------------------------------------------------------------- // -- MARK: Display results // deno-lint-ignore no-explicit-any const getVulnDetails = async (vuln: { id: string }): Promise<any> => { const resp = await fetch(`${OSV_API_BASE}/vulns/${vuln.id}`, { method: "GET", headers: { "Accept": "application/json", }, }); return await resp.json(); }; let not_vulnerable = 0; const vuln_crates = []; for (let i = 0; i < results.length; i++) { const resp = results[i]; for (let j = 0; j < resp.length; j++) { const crate = all_batches[i][j]; if (Object.keys(resp[j]).length === 0) { not_vulnerable++; } else { vuln_crates.push([crate, resp[j]]); } } } console.error( `Finished: ${not_vulnerable} packages with no known vulnerabilities.`, ); console.error(`Found ${vuln_crates.length} vulnerable packages.`); if (vuln_crates.length > 0) { for (let i = 0; i < vuln_crates.length; i++) { const [crate, resp] = vuln_crates[i]; const vulns: { id: string }[] = resp["vulns"]; const vulnDetails: { id: string; aliases: string[]; summary: string; }[] = await Promise.all(vulns.map(getVulnDetails)); // remove duplicate vulnerabilities by looking at aliases let numDupes = 0; const aliases: string[] = []; for (let j = 0; j < vulnDetails.length; j++) { if (aliases.includes(vulnDetails[j].id)) { numDupes++; continue; } aliases.push(...vulnDetails[j].aliases); } // Now report the vulnerabilities const dupeInfo = numDupes == 0 ? "" : numDupes == 1 ? " (1 dupe)" : ` (${numDupes} dupes)`; console.error( ` ${crate.package.purl}-${crate.version}: ${vulns.length} advisories${dupeInfo}`, ); for (let j = 0; j < vulns.length; j++) { if (aliases.includes(vulns[j].id)) continue; const details = vulnDetails[j]; console.error(` - ${details.id}: ${details.summary}`); console.error( ` <https://osv.dev/vulnerability/${details.id}>`, ); } } Deno.exit(1); }