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
159 changes: 159 additions & 0 deletions entity/src/exploit_intelligence_job.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/// Entity model for tracking Exploit Intelligence analysis jobs.
///
/// Each row represents a single analysis request submitted to the Exploit Intelligence
/// service, tracking its lifecycle from submission through completion. The result may
/// link to an ingested VEX advisory or report an error.
use sea_orm::entity::prelude::*;
use time::OffsetDateTime;

/// Lifecycle status of an Exploit Intelligence analysis job.
#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
enum_name = "exploit_intelligence_job_status"
)]
pub enum ExploitIntelligenceJobStatus {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use actual enum types:

  • #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, EnumIter, DeriveActiveEnum)]
    #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "score_type")]
    pub enum ScoreType {
    #[sea_orm(string_value = "2.0")]
    V2_0,
    #[sea_orm(string_value = "3.0")]
    V3_0,
    #[sea_orm(string_value = "3.1")]
    V3_1,
    #[sea_orm(string_value = "4.0")]
    V4_0,
    }
  • create_enum_if_not_exists(
    manager,
    Severity::Table,
    Severity::VARIANTS.iter().skip(1).copied(),
    )
    .await?;

/// Job has been created but not yet started.
#[sea_orm(string_value = "pending")]
Pending,
/// Analysis is in progress.
#[sea_orm(string_value = "running")]
Running,
/// Analysis completed successfully.
#[sea_orm(string_value = "completed")]
Completed,
/// Analysis failed with an error.
#[sea_orm(string_value = "failed")]
Failed,
}

/// Analysis finding from the Exploit Intelligence service.
///
/// Represents the outcome of a vulnerability analysis for a given SBOM/CVE pair.
#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "ei_finding")]
pub enum Finding {
/// The analysed component is vulnerable to the CVE.
#[sea_orm(string_value = "vulnerable")]
Vulnerable,
/// The analysed component is not vulnerable to the CVE.
#[sea_orm(string_value = "not_vulnerable")]
NotVulnerable,
/// The analysis was inconclusive.
#[sea_orm(string_value = "uncertain")]
Uncertain,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "exploit_intelligence_job")]
pub struct Model {
/// The database internal ID.
#[sea_orm(primary_key)]
pub id: Uuid,

/// Correlation ID from the Exploit Intelligence service (unique, nullable).
///
/// Initially `None` when the job is created; populated once the EI service
/// returns a scan identifier. For multi-component SPDX jobs this stays `None`
/// because individual scan IDs live on the component records instead.
#[sea_orm(unique)]
pub scan_id: Option<String>,

/// FK to the SBOM being analysed.
pub sbom_id: Uuid,

/// CVE identifier (e.g., "CVE-2024-9680").
pub vulnerability_id: String,

/// Current lifecycle status of the job.
pub status: ExploitIntelligenceJobStatus,

/// Git repository URL for source code analysis.
pub source_url: Option<String>,

/// Link to the EI human-readable justification report.
///
/// Set for single-component (CycloneDX) jobs. For multi-component SPDX jobs
/// this stays `None` — per-component report URLs live on the component records.
pub report_url: Option<String>,

/// FK to advisory — set when a VEX document is ingested from the result.
///
/// Set for single-component (CycloneDX) jobs. For multi-component SPDX jobs
/// this stays `None` — each component carries its own advisory link.
pub advisory_id: Option<Uuid>,

/// Analysis finding for the vulnerability (set on completion).
///
/// For single-component jobs this is the direct EI result. For multi-component
/// SPDX jobs this is an aggregate: `Vulnerable` if any component is vulnerable,
/// `NotVulnerable` if all are not, `Uncertain` otherwise.
pub finding: Option<Finding>,

/// Error details for failed analyses.
pub error_message: Option<String>,

/// EI product ID for SPDX multi-component flow (`None` for single-component).
///
/// When set, per-component results are tracked via the related
/// [`exploit_intelligence_job_component`](super::exploit_intelligence_job_component) records.
pub product_id: Option<String>,

/// Number of components in the product (`None` for single-component).
pub total_components: Option<i32>,

/// Timestamp when the job was created.
pub created: OffsetDateTime,

/// Timestamp when the job was last updated.
pub updated: OffsetDateTime,
}

/// Foreign-key relationships for the exploit intelligence job entity.
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
/// Link to the SBOM that was analysed.
#[sea_orm(
belongs_to = "super::sbom::Entity",
from = "Column::SbomId",
to = "super::sbom::Column::SbomId"
)]
Sbom,

/// Link to the advisory ingested from the analysis result.
#[sea_orm(
belongs_to = "super::advisory::Entity",
from = "Column::AdvisoryId",
to = "super::advisory::Column::Id"
)]
Advisory,

/// Link to the per-component analysis results (multi-component SPDX flow).
#[sea_orm(has_many = "super::exploit_intelligence_job_component::Entity")]
Components,
}

/// Navigate from an exploit intelligence job to its SBOM.
impl Related<super::sbom::Entity> for Entity {
fn to() -> RelationDef {
Relation::Sbom.def()
}
}

/// Navigate from an exploit intelligence job to its advisory.
impl Related<super::advisory::Entity> for Entity {
fn to() -> RelationDef {
Relation::Advisory.def()
}
}

/// Navigate from an exploit intelligence job to its components.
impl Related<super::exploit_intelligence_job_component::Entity> for Entity {
fn to() -> RelationDef {
Relation::Components.def()
}
}

/// Default active-model behaviour (no custom hooks).
impl ActiveModelBehavior for ActiveModel {}
89 changes: 89 additions & 0 deletions entity/src/exploit_intelligence_job_component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/// Entity model for tracking per-component analysis results within a multi-component
/// Exploit Intelligence product job.
///
/// Each row represents one container image component within an SPDX product SBOM,
/// independently analysed by the Exploit Intelligence service. The parent
/// `exploit_intelligence_job` holds product-level metadata while this entity holds
/// the per-component scan results.
use super::exploit_intelligence_job::{ExploitIntelligenceJobStatus, Finding};
use sea_orm::entity::prelude::*;
use time::OffsetDateTime;

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "exploit_intelligence_job_component")]
pub struct Model {
/// The database internal ID.
#[sea_orm(primary_key)]
pub id: Uuid,

/// FK to the parent exploit intelligence job.
pub job_id: Uuid,

/// Identifier of the component (purl or container image reference).
pub component_ref: String,

/// Human-readable component name.
pub component_name: String,

/// EI scan ID for this component's report (unique, nullable).
#[sea_orm(unique)]
pub scan_id: Option<String>,

/// Current lifecycle status of this component's analysis.
pub status: ExploitIntelligenceJobStatus,

/// Analysis finding for the component (set on completion).
pub finding: Option<Finding>,

/// Link to the EI human-readable justification report.
pub report_url: Option<String>,

/// FK to advisory — set when a VEX document is ingested from the result.
pub advisory_id: Option<Uuid>,

/// Error details for failed analyses.
pub error_message: Option<String>,

/// Timestamp when the component record was created.
pub created: OffsetDateTime,

/// Timestamp when the component record was last updated.
pub updated: OffsetDateTime,
}

/// Foreign-key relationships for the exploit intelligence job component entity.
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
/// Link to the parent exploit intelligence job.
#[sea_orm(
belongs_to = "super::exploit_intelligence_job::Entity",
from = "Column::JobId",
to = "super::exploit_intelligence_job::Column::Id"
)]
Job,

/// Link to the advisory ingested from the analysis result.
#[sea_orm(
belongs_to = "super::advisory::Entity",
from = "Column::AdvisoryId",
to = "super::advisory::Column::Id"
)]
Advisory,
}

/// Navigate from a component to its parent job.
impl Related<super::exploit_intelligence_job::Entity> for Entity {
fn to() -> RelationDef {
Relation::Job.def()
}
}

/// Navigate from a component to its advisory.
impl Related<super::advisory::Entity> for Entity {
fn to() -> RelationDef {
Relation::Advisory.def()
}
}

/// Default active-model behaviour (no custom hooks).
impl ActiveModelBehavior for ActiveModel {}
2 changes: 2 additions & 0 deletions entity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pub mod advisory_vulnerability_score;
pub mod base_purl;
pub mod cpe;
pub mod expanded_license;
pub mod exploit_intelligence_job;
pub mod exploit_intelligence_job_component;
pub mod importer;
pub mod importer_report;
pub mod labels;
Expand Down
2 changes: 2 additions & 0 deletions migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ mod m0002160_fix_ref_fk;
mod m0002170_drop_cvss_tables;
mod m0002180_advisory_fk_indexes;
mod m0002190_vulnerability_base_score_advisory;
mod m0002200_create_exploit_intelligence_job;
mod m0002200_source_document_ingested_index;
mod m0002210_sbom_node_name_index;

Expand Down Expand Up @@ -141,6 +142,7 @@ impl MigratorExt for Migrator {
.normal(m0002130_sbom_ancestor::Migration)
.normal(m0002200_source_document_ingested_index::Migration)
.normal(m0002210_sbom_node_name_index::Migration)
.normal(m0002200_create_exploit_intelligence_job::Migration)
}
}

Expand Down
Loading
Loading