Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ RUN apt-get update && apt-get install -y \
libarchive-tools \
xz-utils \
libatomic1 \
libkrb5-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& update-ca-certificates
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ dependencies = [
"litellm<=1.75.8",
"csaf-tool==0.3.2",
"jsonschema>=4.0.0,<5.0.0",
"koji",
"unidiff>=0.7.5",
]
requires-python = ">=3.11,<3.13"
description = "NVIDIA AI Blueprint: Vulnerability Analysis for Container Security"
Expand Down
72 changes: 72 additions & 0 deletions src/exploit_iq_commons/data_models/checker_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from enum import Enum
from enum import IntEnum
from pathlib import Path

from pydantic import BaseModel, Field


class PackageCheckerStatus(IntEnum):
"""Per-CVE status codes produced by the PackageIdentify phase."""
OK = 0
ERROR_PKG_IDENT_NO_INTEL = 1
PKG_IDENT_NOT_VUL = 2
ERROR_FAILED_TO_DOWNLOAD_SRPM = 3


PACKAGE_CHECKER_STATUS_DESCRIPTIONS: dict[PackageCheckerStatus, str] = {
PackageCheckerStatus.OK:
"Package identified and in affected range -- continue investigation",
PackageCheckerStatus.ERROR_PKG_IDENT_NO_INTEL:
"No Intel found for the package",
PackageCheckerStatus.PKG_IDENT_NOT_VUL:
"Identification state concluded from intel that target package is not vulnerable",
PackageCheckerStatus.ERROR_FAILED_TO_DOWNLOAD_SRPM:
"Failed to download the patched SRPM",
}

class EnumIdentifyResult(str, Enum):
"""Result of the PackageIdentify phase for a single CVE."""
YES = "yes"
NO = "no"
UNKNOWN = "unknown"

class PackageIdentifyResult(BaseModel):
"""Result of the PackageIdentify phase for a single CVE."""
affected_rpm_list: list[str] = []
fixed_rpm_list: list[str] = []

is_target_package_affected: EnumIdentifyResult = EnumIdentifyResult.UNKNOWN
is_target_package_fixed: EnumIdentifyResult = EnumIdentifyResult.UNKNOWN



class AcquiredArtifacts(BaseModel):
"""Resolved file locations populated by source_acquisition, consumed by downstream checker nodes."""
srpm_path: Path | None = None
source_dir: Path | None = None
build_log_path: Path | None = None
binary_rpm_path: Path | None = None
patch_source_dir: Path | None = None
patch_diff_path: Path | None = None


class PackageCheckerContext(BaseModel):
"""Consolidates all checker-specific state on AgentMorpheusInfo."""
status: PackageCheckerStatus | None = None
source_key: str | None = None
artifacts: AcquiredArtifacts = Field(default_factory=AcquiredArtifacts)
identify_result: PackageIdentifyResult = Field(default_factory=PackageIdentifyResult)
22 changes: 21 additions & 1 deletion src/exploit_iq_commons/data_models/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ class AnalysisType(str, Enum):
IMAGE = "image"
SOURCE = "source"


class PipelineMode(str, Enum):
"""
Controls which investigation path the pipeline takes after process_sbom.
Orthogonal to AnalysisType (input format) -- any combination is valid.
"""
FULL_PIPELINE = "full_pipeline"
PACKAGE_CHECKER = "package_checker"



class HashableModel(BaseModel):
"""
Subclass of a Pydantic BaseModel that is hashable. Use in objects that need to be hashed for caching purposes.
Expand All @@ -50,7 +61,16 @@ def __ne__(self, other):
def __gt__(self, other):
return self.__hash__() > other.__hash__()


class TargetPackage(HashableModel):
"""
A package to investigate.
"""
name: str
version: str | None = None
release: str | None = None # e.g. "1.el8_2.3" (needed for Brew NVR lookup)
ecosystem: str | None = None
arch: str = "x86_64" # e.g. "x86_64", "aarch64", "s390x", "noarch"

class TypedBaseModel(BaseModel, typing.Generic[_LT]):
"""
Subclass of Pydantic BaseModel that allows for specifying the object type. Use in Pydantic discriminated unions.
Expand Down
4 changes: 2 additions & 2 deletions src/exploit_iq_commons/data_models/cve_intel.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ class CVSSV3(BaseModel):

class BaseMetricV3(BaseModel):
cvssV3: "CVSSV3"
exploitabilityScore: float
impactScore: float
exploitabilityScore: float | None = None
impactScore: float | None = None

class Impact(BaseModel):
baseMetricV3: "BaseMetricV3"
Expand Down
2 changes: 2 additions & 0 deletions src/exploit_iq_commons/data_models/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from pydantic import BaseModel

from exploit_iq_commons.data_models.checker_status import PackageCheckerContext
from exploit_iq_commons.data_models.cve_intel import CveIntel
from exploit_iq_commons.data_models.dependencies import VulnerableDependencies

Expand Down Expand Up @@ -62,3 +63,4 @@ class SBOMInfo(BaseModel):
intel: list[CveIntel] | None = None
sbom: SBOMInfo | None = None
vulnerable_dependencies: list[VulnerableDependencies] | None = None
checker_context: PackageCheckerContext | None = None
16 changes: 16 additions & 0 deletions src/exploit_iq_commons/data_models/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@
from pydantic import Field
from pydantic import Tag
from pydantic import field_validator
from pydantic import model_validator

from exploit_iq_commons.utils.string_utils import is_valid_cve_id
from exploit_iq_commons.utils.string_utils import is_valid_ghsa_id
from exploit_iq_commons.utils.dep_tree import Ecosystem
from exploit_iq_commons.data_models.common import AnalysisType
from exploit_iq_commons.data_models.common import HashableModel
from exploit_iq_commons.data_models.common import PipelineMode , TargetPackage
from exploit_iq_commons.data_models.common import TypedBaseModel
from exploit_iq_commons.data_models.info import AgentMorpheusInfo
from exploit_iq_commons.data_models.info import SBOMPackage
Expand Down Expand Up @@ -168,9 +170,23 @@ class ImageInfoInput(HashableModel):
- "source": Analysis of source code and commitId without SBOM data
"""

pipeline_mode: PipelineMode = PipelineMode.FULL_PIPELINE
"""
Controls which investigation path the pipeline takes after process_sbom:
- "full_pipeline": Full transitive analysis (check_vuln_deps -> llm_engine)
- "package_checker": Focused package vulnerability checker (package_checker -> checker_output)
"""
target_package: TargetPackage | None = None

source_info: list[SourceDocumentsInfo]
sbom_info: SBOMInfoInput | None = None

@model_validator(mode="after")
def validate_target_package(self) -> "ImageInfoInput":
if self.pipeline_mode == PipelineMode.PACKAGE_CHECKER and self.target_package is None:
raise ValueError("target_package is required when pipeline_mode is PACKAGE_CHECKER")
return self

@field_validator('source_info', mode='after')
@classmethod
def check_conflicting_refs(cls, source_info: list[SourceDocumentsInfo]) -> list[SourceDocumentsInfo]:
Expand Down
3 changes: 2 additions & 1 deletion src/exploit_iq_commons/utils/source_rpm_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,8 @@ def parse_sbom(self):
logger.info(f"Found {len(packages)} packages in SBOM, platform: {platform_version}")
return packages, platform_version

def extract_src_rpm(self, rpm_path: Path, extract_dir: Path):
@staticmethod
def extract_src_rpm(rpm_path: Path, extract_dir: Path):
#logger.info(f" Extracting {rpm_path.name} to {extract_dir} ...")
extract_dir.mkdir(parents=True, exist_ok=True)
try:
Expand Down
24 changes: 24 additions & 0 deletions src/vuln_analysis/configs/brew/internal-user-profile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Internal User Profile — Red Hat VPN-connected environment
#
# Assumptions:
# - User is on the Red Hat VPN (can reach *.redhat.com internal hosts)
# - Build logs are available via Brew task output

profile:
name: redhat-internal

hosts:
rpm:
brew_hub: https://brewhub.engineering.redhat.com/brewhub
brew_download: https://download-01.beak-001.prod.iad2.dc.redhat.com/brewroot
git:
dist_git: https://pkgs.devel.redhat.com/cgit

default_arch: x86_64

ssl_verify: false

build_log:
auto_fetch: true

download_binary_rpm: false
26 changes: 26 additions & 0 deletions src/vuln_analysis/configs/config-http-openai.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ functions:
Code Keyword Search:
_type: lexical_code_search
top_k: 5
Source Grep:
_type: source_grep
base_checker_dir: .cache/am_cache/checker
max_results: 50
context_lines: 2
CVE Web Search:
_type: serp_wrapper
max_retries: 5
Expand Down Expand Up @@ -148,6 +153,23 @@ functions:
generate_intel_score: true
intel_low_score: 51
insist_analysis: false
cve_source_acquisition:
_type: cve_source_acquisition
base_git_dir: .cache/am_cache/git
base_pickle_dir: .cache/am_cache/pickle
base_rpm_dir: .cache/am_cache/rpms
cve_checker_segmentation:
_type: cve_checker_segmentation
base_checker_dir: .cache/am_cache/checker
base_code_index_dir: .cache/am_cache/code_index
cve_package_checker_probe:
_type: cve_package_checker_probe
probe_log_path: .cache/am_cache/checker/probe_results.jsonl
cve_package_code_agent:
_type: cve_package_code_agent
llm_name: cve_agent_executor_llm
base_checker_dir: .cache/am_cache/checker
base_code_index_dir: .cache/am_cache/code_index
health_check:
_type: health_check

Expand Down Expand Up @@ -239,6 +261,10 @@ workflow:
cve_summarize_name: cve_summarize
cve_justify_name: cve_justify
cve_output_config_name: cve_http_output
cve_source_acquisition_name: cve_source_acquisition
cve_checker_segmentation_name: cve_checker_segmentation
cve_package_checker_probe_name: cve_package_checker_probe
cve_package_code_agent_name: cve_package_code_agent

eval:
general:
Expand Down
Loading