Skip to main content

uv_audit/
types.rs

1//! Types for interacting with dependency audits.
2
3use jiff::Timestamp;
4use uv_normalize::PackageName;
5use uv_pep440::Version;
6use uv_redacted::DisplaySafeUrl;
7use uv_small_str::SmallString;
8
9/// Represents a resolved dependency, with a normalized name and PEP 440 version.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct Dependency {
12    name: PackageName,
13    version: Version,
14}
15
16impl Dependency {
17    /// Create a new dependency with the given name and version.
18    pub fn new(name: PackageName, version: Version) -> Self {
19        Self { name, version }
20    }
21
22    /// Get the package name.
23    pub fn name(&self) -> &PackageName {
24        &self.name
25    }
26
27    /// Get the version.
28    pub fn version(&self) -> &Version {
29        &self.version
30    }
31}
32
33/// An opaque identifier for a vulnerability. These are conventionally
34/// formatted as `SRC-XXXX-YYYY`, where `SRC` is an identifier for the vulnerability source,
35/// `XXXX` is typically a year or other "bucket" identifier, and `YYYY` is a unique identifier
36/// within that bucket. For example, `CVE-2026-12345` or `PYSEC-2023-0001`.
37///
38/// No assumptions should be made about the format of these identifiers.
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub struct VulnerabilityID(SmallString);
41
42impl VulnerabilityID {
43    /// Create a new vulnerability ID from a string.
44    pub fn new(id: impl Into<SmallString>) -> Self {
45        Self(id.into())
46    }
47
48    /// Get the string representation of this vulnerability ID.
49    pub fn as_str(&self) -> &str {
50        self.0.as_ref()
51    }
52}
53
54/// Represents an "adverse" project status, i.e. a status that indicates that
55/// a downstream user of the project should review their use of the project
56/// and consider removing it.
57///
58/// These are a subset of the possible project statuses defined in [PEP 792].
59///
60/// [PEP 792]: https://peps.python.org/pep-0792/
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum AdverseStatus {
63    /// The project is archived, meaning it is read-only and no longer maintained.
64    Archived,
65    /// The project is considered generally unsafe for use, e.g. due to malware.
66    Quarantined,
67    /// The project is considered obsolete, and may have been superseded by another project.
68    Deprecated,
69}
70
71impl std::fmt::Display for AdverseStatus {
72    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        formatter.write_str(match self {
74            Self::Archived => "archived",
75            Self::Quarantined => "quarantined",
76            Self::Deprecated => "deprecated",
77        })
78    }
79}
80
81/// A vulnerability within a dependency.
82#[derive(Debug)]
83pub struct Vulnerability {
84    /// The dependency that is vulnerable.
85    pub dependency: Dependency,
86    /// The unique identifier for the vulnerability.
87    pub id: VulnerabilityID,
88    /// A short, human-readable summary of the vulnerability, if available.
89    pub summary: Option<String>,
90    /// A full-length description of the vulnerability, if available.
91    pub description: Option<String>,
92    /// A link to more information about the vulnerability, if available.
93    pub link: Option<DisplaySafeUrl>,
94    /// Zero or more versions that fix the vulnerability.
95    pub fix_versions: Vec<Version>,
96    /// Zero or more aliases for this vulnerability in other databases.
97    pub aliases: Vec<VulnerabilityID>,
98    /// The timestamp when this vulnerability was published, if available.
99    pub published: Option<Timestamp>,
100    /// The timestamp when this vulnerability was last modified, if available.
101    pub modified: Option<Timestamp>,
102}
103
104impl Vulnerability {
105    pub(crate) fn new(
106        dependency: Dependency,
107        id: VulnerabilityID,
108        summary: Option<String>,
109        description: Option<String>,
110        link: Option<DisplaySafeUrl>,
111        fix_versions: Vec<Version>,
112        aliases: Vec<VulnerabilityID>,
113        published: Option<Timestamp>,
114        modified: Option<Timestamp>,
115    ) -> Self {
116        // Vulnerability summaries often contain excess whitespace, as well as newlines.
117        // We normalize these out.
118        let summary = summary.map(|summary| summary.trim().replace('\n', ""));
119
120        Self {
121            dependency,
122            id,
123            summary,
124            description,
125            link,
126            fix_versions,
127            aliases,
128            published,
129            modified,
130        }
131    }
132
133    /// Return an iterator over all identifiers for this vulnerability, including the primary ID and all aliases.
134    fn ids(&self) -> impl Iterator<Item = &VulnerabilityID> {
135        std::iter::once(&self.id).chain(self.aliases.iter())
136    }
137
138    /// Returns `true` if any of this vulnerability's identifiers (primary ID or aliases) match the given ID.
139    pub fn matches(&self, id: &VulnerabilityID) -> bool {
140        self.ids().any(|own_id| own_id == id)
141    }
142
143    /// Pick the subjectively "best" identifier for this vulnerability.
144    /// For our purposes we prefer PYSEC IDs, then GHSA, then CVE, then whatever
145    /// primary ID the vulnerability came with.
146    pub fn best_id(&self) -> &VulnerabilityID {
147        self.ids()
148            .find(|id| {
149                id.as_str().starts_with("PYSEC-")
150                    || id.as_str().starts_with("GHSA-")
151                    || id.as_str().starts_with("CVE-")
152            })
153            .unwrap_or(&self.id)
154    }
155}
156
157/// An adverse project status, such as an archived or deprecated project.
158///
159/// PEP 792 status markers are project-level, so this finding carries only the
160/// project name — not a specific version.
161#[derive(Debug)]
162pub struct ProjectStatus {
163    /// The name of the project with the adverse status.
164    pub name: PackageName,
165    /// The adverse status of the project.
166    pub status: AdverseStatus,
167    /// An optional (index-supplied) reason for the adverse status.
168    pub reason: Option<String>,
169}
170
171/// Represents a finding on a dependency.
172#[derive(Debug)]
173pub enum Finding {
174    Vulnerability(Box<Vulnerability>),
175    ProjectStatus(ProjectStatus),
176}