From cec6d3968e52636e8164e0036f419105731329ff Mon Sep 17 00:00:00 2001 From: kateeselius Date: Wed, 5 Nov 2025 11:26:23 -0500 Subject: [PATCH] feat: POC support from JVM release file parsing --- lib/analyzer/java-runtime/index.ts | 24 ++++++++++++++++++ lib/analyzer/java-runtime/parser.ts | 39 +++++++++++++++++++++++++++++ lib/analyzer/static-analyzer.ts | 5 ++++ lib/analyzer/types.ts | 2 ++ lib/facts.ts | 16 ++++++++++++ lib/inputs/java-runtime/static.ts | 32 +++++++++++++++++++++++ lib/response-builder.ts | 8 ++++++ lib/types.ts | 2 ++ 8 files changed, 128 insertions(+) create mode 100644 lib/analyzer/java-runtime/index.ts create mode 100644 lib/analyzer/java-runtime/parser.ts create mode 100644 lib/inputs/java-runtime/static.ts diff --git a/lib/analyzer/java-runtime/index.ts b/lib/analyzer/java-runtime/index.ts new file mode 100644 index 000000000..2e941cc29 --- /dev/null +++ b/lib/analyzer/java-runtime/index.ts @@ -0,0 +1,24 @@ +import { JavaRuntimeMetadata } from "../../facts"; +import { ExtractedLayers } from "../../extractor/types"; +import { getJavaRuntimeReleaseContent } from "../../inputs/java-runtime/static"; +import { parseJavaRuntimeRelease } from "./parser"; + +/** + * Detects Java runtime metadata from the /opt/java/openjdk/release file. + * This provides version, implementor, image type (JRE/JDK), and modules information, + * + * @param extractedLayers - Extracted image layers containing file contents + * @returns Parsed Java runtime metadata or null if not found/parseable + */ +export function detectJavaRuntime( + extractedLayers: ExtractedLayers, +): JavaRuntimeMetadata | null { + const releaseContent = getJavaRuntimeReleaseContent(extractedLayers); + + if (!releaseContent) { + return null; + } + + return parseJavaRuntimeRelease(releaseContent); +} + diff --git a/lib/analyzer/java-runtime/parser.ts b/lib/analyzer/java-runtime/parser.ts new file mode 100644 index 000000000..43019a333 --- /dev/null +++ b/lib/analyzer/java-runtime/parser.ts @@ -0,0 +1,39 @@ +import { JavaRuntimeMetadata } from "../../facts"; + +/** + * Parses the Java runtime release file content into structured metadata. + * + * The release file format is key="value" pairs, one per line: + * IMPLEMENTOR="Eclipse Adoptium" + * JAVA_VERSION="17.0.11" + * IMAGE_TYPE="JRE" + * MODULES="java.base java.logging java.xml ..." + * + * @param content - Raw content of /opt/java/openjdk/release file + * @returns Parsed metadata or null if parsing fails + */ +export function parseJavaRuntimeRelease( + content: string, +): JavaRuntimeMetadata | null { + if (!content || content.trim().length === 0) { + return null; + } + + try { + // TODO: Implement actual parsing logic + // For now, return null to indicate parsing not yet implemented + // + // Implementation should: + // 1. Split content by newlines + // 2. Parse each line as KEY="VALUE" or KEY=VALUE + // 3. Extract JAVA_VERSION, IMPLEMENTOR, IMAGE_TYPE, MODULES + // 4. Split MODULES by spaces into an array + // 5. Return JavaRuntimeMetadata object with the extracted information + + return null; + } catch (error) { + // If parsing fails, return null + return null; + } +} + diff --git a/lib/analyzer/static-analyzer.ts b/lib/analyzer/static-analyzer.ts index 3a83db484..2f14ce696 100644 --- a/lib/analyzer/static-analyzer.ts +++ b/lib/analyzer/static-analyzer.ts @@ -29,6 +29,8 @@ import { getJarFileContentAction, getUsrLibJarFileContentAction, } from "../inputs/java/static"; +import { getJavaRuntimeReleaseAction } from "../inputs/java-runtime/static"; +import { detectJavaRuntime } from "./java-runtime"; import { getNodeAppFileContentAction, getNodeJsTsAppFileContentAction, @@ -99,6 +101,7 @@ export async function analyze( ...getOsReleaseActions, getNodeBinariesFileContentAction, getOpenJDKBinariesFileContentAction, + getJavaRuntimeReleaseAction, getDpkgPackageFileContentAction, getRedHatRepositoriesContentAction, ]; @@ -222,6 +225,7 @@ export async function analyze( } const binaries = getBinariesHashes(extractedLayers); + const javaRuntimeMetadata = detectJavaRuntime(extractedLayers) || undefined; const applicationDependenciesScanResults: AppDepsScanResultWithoutTarget[] = []; @@ -298,6 +302,7 @@ export async function analyze( platform, results, binaries, + javaRuntimeMetadata, imageLayers: manifestLayers, rootFsLayers, applicationDependenciesScanResults, diff --git a/lib/analyzer/types.ts b/lib/analyzer/types.ts index c95883ddd..cc28801ba 100644 --- a/lib/analyzer/types.ts +++ b/lib/analyzer/types.ts @@ -1,5 +1,6 @@ import { ImageName } from "../extractor/image"; import { AutoDetectedUserInstructions, ManifestFile } from "../types"; +import { JavaRuntimeMetadata } from "../facts"; import { AppDepsScanResultWithoutTarget, JarCoords, @@ -74,6 +75,7 @@ export interface StaticAnalysis { osRelease: OSRelease; results: ImageAnalysis[]; binaries: string[]; + javaRuntimeMetadata?: JavaRuntimeMetadata; imageLayers: string[]; rootFsLayers?: string[]; autoDetectedUserInstructions?: AutoDetectedUserInstructions; diff --git a/lib/facts.ts b/lib/facts.ts index 6f98a373f..fc60da6ad 100644 --- a/lib/facts.ts +++ b/lib/facts.ts @@ -103,3 +103,19 @@ export interface OCIDistributionMetadataFact { type: "ociDistributionMetadata"; data: OCIDistributionMetadata; } + +export interface JavaRuntimeMetadata { + version: string; + // the vendor/organization that provided the Java runtime (needed because sometimes CVEs are vendor specific) + implementor?: string; + // the type of Java runtime (JRE or JDK) + imageType?: "JRE" | "JDK"; + // the modules that are included in the Java runtime + modules?: string[]; + releaseFilePath: string; +} + +export interface JavaRuntimeMetadataFact { + type: "javaRuntimeMetadata"; + data: JavaRuntimeMetadata; +} diff --git a/lib/inputs/java-runtime/static.ts b/lib/inputs/java-runtime/static.ts new file mode 100644 index 000000000..8c1cae8eb --- /dev/null +++ b/lib/inputs/java-runtime/static.ts @@ -0,0 +1,32 @@ +import { normalize as normalizePath } from "path"; +import { getContentAsString } from "../../extractor"; +import { ExtractAction, ExtractedLayers } from "../../extractor/types"; +import { streamToString } from "../../stream-utils"; + +/** + * Extract action to detect and read the Java runtime release file. + * This file contains metadata about the Java installation including version, + * implementor, image type (JRE/JDK), and modules (important for jlink custom JREs). + */ +export const getJavaRuntimeReleaseAction: ExtractAction = { + actionName: "java-runtime-release", + // using this specific path for Java runtime metadata for now, but might want to expand this to other paths in the future. + filePathMatches: (filePath) => + filePath === normalizePath("/opt/java/openjdk/release"), + callback: streamToString, +}; + +/** + * Retrieves the Java runtime release file content from extracted layers. + * Returns the raw file content as a string, or an empty string if not found. + */ +export function getJavaRuntimeReleaseContent( + extractedLayers: ExtractedLayers, +): string { + const content = getContentAsString( + extractedLayers, + getJavaRuntimeReleaseAction, + ); + return content || ""; +} + diff --git a/lib/response-builder.ts b/lib/response-builder.ts index d4a11b623..47afa3dee 100644 --- a/lib/response-builder.ts +++ b/lib/response-builder.ts @@ -48,6 +48,14 @@ async function buildResponse( }; additionalFacts.push(keyBinariesHashesFact); } + // add the Java Runtime Metadata fact as a part of the first scan result + if (depsAnalysis.javaRuntimeMetadata) { + const javaRuntimeMetadataFact: facts.JavaRuntimeMetadataFact = { + type: "javaRuntimeMetadata", + data: depsAnalysis.javaRuntimeMetadata, + }; + additionalFacts.push(javaRuntimeMetadataFact); + } if (dockerfileAnalysis !== undefined) { const dockerfileAnalysisFact: facts.DockerfileAnalysisFact = { diff --git a/lib/types.ts b/lib/types.ts index d5f4142ba..d65c059e1 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -69,6 +69,8 @@ export type FactType = | "jarFingerprints" // Hashes of executables not installed by a package manager (e.g. if they were copied straight onto the image). | "keyBinariesHashes" + // Java runtime metadata extracted from /opt/java/openjdk/release file + | "javaRuntimeMetadata" | "loadedPackages" | "ociDistributionMetadata" | "rootFs"