UNPKG

snyk-docker-plugin

Version:
113 lines (102 loc) 3.7 kB
import * as Debug from "debug"; import { normalize as normalizePath } from "path"; import { ChiselPackage } from "../../analyzer/types"; import { decompressZstd } from "../../compression-utils"; import { getContentAsBuffer } from "../../extractor"; import { ExtractAction, ExtractedLayers } from "../../extractor/types"; import { streamToBuffer } from "../../stream-utils"; const debug = Debug("snyk"); /** * Path to the Chisel manifest file within container images. * This file contains package and slice information for Chisel-built images. */ const CHISEL_MANIFEST_PATH = "/var/lib/chisel/manifest.wall"; /** * Extract action for Ubuntu Chisel manifest files. * * Chisel is Ubuntu's tool for creating minimal container images by installing * only specific "slices" of Debian packages. The manifest.wall file is a * zstd-compressed NDJSON (newline-delimited JSON) file that records all * installed packages, slices, and files for integrity verification and SBOM generation. * * See: https://documentation.ubuntu.com/chisel/en/latest/reference/manifest/ */ export const getChiselManifestAction: ExtractAction = { actionName: "chisel-manifest", filePathMatches: (filePath) => filePath === normalizePath(CHISEL_MANIFEST_PATH), callback: streamToBuffer, }; /** * Extracts and parses Chisel package information from Docker image layers. * * Searches for the Chisel manifest file (/var/lib/chisel/manifest.wall), decompresses it, * and extracts package entries. The manifest uses NDJSON format where each line is a * separate JSON object with a "kind" field indicating the entry type. * * @param extractedLayers - Layers extracted from the Docker image * @returns Array of Chisel packages found in the manifest, or empty array if not found */ export function getChiselManifestContent( extractedLayers: ExtractedLayers, ): ChiselPackage[] { const compressedManifest = getContentAsBuffer( extractedLayers, getChiselManifestAction, ); if (!compressedManifest) { return []; } try { const decompressed = decompressZstd(compressedManifest); const manifestText = decompressed.toString("utf8"); const packages: ChiselPackage[] = []; const lines = manifestText.split("\n"); for (const line of lines) { if (!line.trim()) { continue; } try { const entry = JSON.parse(line); // Only extract package entries; manifest also contains "slice", "path", and "content" entries if (entry.kind === "package") { // Validate required fields exist before creating package object if (!entry.name || !entry.version || !entry.sha256 || !entry.arch) { debug( `Skipping package entry with missing required fields: ${JSON.stringify( entry, )}`, ); continue; } packages.push({ kind: entry.kind, name: entry.name, version: entry.version, sha256: entry.sha256, arch: entry.arch, }); } } catch (parseError) { // Skip malformed JSON lines - manifest may be corrupted or have trailing newlines debug( `Failed to parse Chisel manifest line: ${ parseError instanceof Error ? parseError.message : String(parseError) }`, ); continue; } } debug(`Found ${packages.length} Chisel packages in manifest`); return packages; } catch (error) { debug( `Failed to process Chisel manifest: ${ error instanceof Error ? error.message : String(error) }`, ); return []; } }