diff --git a/compiler-cli/src/dependencies.rs b/compiler-cli/src/dependencies.rs index a7d9f35c35d..c8c92dd837b 100644 --- a/compiler-cli/src/dependencies.rs +++ b/compiler-cli/src/dependencies.rs @@ -907,12 +907,14 @@ impl ProvidedPackage { requirements, retirement_status: None, outer_checksum: vec![], + security_advisories: vec![], meta: (), }; hexpm::Package { name: name.as_str().into(), repository: "local".into(), releases: vec![release], + advisories: vec![], } } diff --git a/compiler-cli/src/dependencies/tests.rs b/compiler-cli/src/dependencies/tests.rs index 873123040fa..7287fa8ee8c 100644 --- a/compiler-cli/src/dependencies/tests.rs +++ b/compiler-cli/src/dependencies/tests.rs @@ -643,10 +643,12 @@ fn provided_local_to_hex() { let hex_package = hexpm::Package { name: "package".into(), repository: "local".into(), + advisories: vec![], releases: vec![hexpm::Release { version: Version::new(1, 0, 0), retirement_status: None, outer_checksum: vec![], + security_advisories: vec![], meta: (), requirements: [ ( @@ -703,11 +705,13 @@ fn provided_git_to_hex() { let hex_package = hexpm::Package { name: "package".into(), repository: "local".into(), + advisories: vec![], releases: vec![hexpm::Release { version: Version::new(1, 0, 0), retirement_status: None, outer_checksum: vec![], meta: (), + security_advisories: vec![], requirements: [ ( "req_1".into(), diff --git a/compiler-core/src/dependency.rs b/compiler-core/src/dependency.rs index 36c65e9c93e..5f1491a4dbe 100644 --- a/compiler-core/src/dependency.rs +++ b/compiler-core/src/dependency.rs @@ -44,11 +44,13 @@ where let root = hexpm::Package { name: root_name.as_str().into(), repository: "local".into(), + advisories: vec![], releases: vec![Release { version: root_version.clone(), outer_checksum: vec![], retirement_status: None, requirements, + security_advisories: vec![], meta: (), }], }; @@ -1172,6 +1174,7 @@ but it is locked to 0.2.0, which is incompatible." requirements: all_requirements, retirement_status: None, outer_checksum: vec![1, 2, 3], + security_advisories: vec![], meta: (), } } @@ -1184,6 +1187,7 @@ but it is locked to 0.2.0, which is incompatible." Rc::new(hexpm::Package { name: package.into(), repository: "hexpm".into(), + advisories: vec![], releases, }), ); diff --git a/hexpm/build.rs b/hexpm/build.rs index 8abe3ae7682..577befeee42 100644 --- a/hexpm/build.rs +++ b/hexpm/build.rs @@ -11,8 +11,10 @@ fn main() { // prost_build::compile_protos( // &[ - // "proto/signed.proto", + // "proto/names.proto", // "proto/package.proto", + // "proto/policy.proto", + // "proto/signed.proto", // "proto/versions.proto", // ], // &["proto/"], diff --git a/hexpm/proto/names.proto b/hexpm/proto/names.proto index 6fe07a40418..6e7df4b1bef 100644 --- a/hexpm/proto/names.proto +++ b/hexpm/proto/names.proto @@ -18,4 +18,13 @@ message Package { // If set, the name of the package repository (NEVER USED, DEPRECATED) // string repository = 2; + + optional Timestamp updated_at = 3; +} + +// Based on google.protobuf.Timestamp +// https://github.com/protocolbuffers/protobuf/blob/v3.15.8/src/google/protobuf/timestamp.proto#L136:L147 +message Timestamp { + required int64 seconds = 1; + required int32 nanos = 2; } diff --git a/hexpm/proto/package.proto b/hexpm/proto/package.proto index db84c65c406..2d1a3213caf 100644 --- a/hexpm/proto/package.proto +++ b/hexpm/proto/package.proto @@ -12,6 +12,8 @@ message Package { required string name = 2; // Name of repository required string repository = 3; + // All security advisories affecting any release of the package + repeated SecurityAdvisory advisories = 4; } message Release { @@ -28,6 +30,11 @@ message Release { // sha256 checksum of outer package tarball // required when encoding but optional when decoding optional bytes outer_checksum = 5; + // Indexes into Package.advisories for advisories affecting this release + repeated uint32 advisory_indexes = 6; + // Release published timestamp. Optional for backwards compatibility — + // clients treat absence as "no information". + optional Timestamp published_at = 7; } message RetirementStatus { @@ -43,6 +50,32 @@ enum RetirementReason { RETIRED_RENAMED = 4; } +message SecurityAdvisory { + // Advisory identifier (e.g. GHSA-xxxx-xxxx-xxxx or CVE-xxxx-xxxxx) + required string id = 1; + // Short description of the advisory + required string summary = 2; + // OSV web URL for the advisory + required string html_url = 3; + // Severity of the advisory + optional AdvisorySeverity severity = 4; + // CVSS score (0.0–10.0) + optional float cvss_score = 5; + // OSV API URL for the advisory + required string api_url = 6; + // Other identifiers for the same vulnerability (e.g. a CVE id when the + // primary id is a GHSA id, or vice versa). + repeated string aliases = 7; +} + +enum AdvisorySeverity { + SEVERITY_NONE = 0; + SEVERITY_LOW = 1; + SEVERITY_MEDIUM = 2; + SEVERITY_HIGH = 3; + SEVERITY_CRITICAL = 4; +} + message Dependency { // Package name of dependency required string package = 1; @@ -56,3 +89,10 @@ message Dependency { // If set, the repository where the dependency is located optional string repository = 5; } + +// Based on google.protobuf.Timestamp +// https://github.com/protocolbuffers/protobuf/blob/v3.15.8/src/google/protobuf/timestamp.proto#L136:L147 +message Timestamp { + required int64 seconds = 1; + required int32 nanos = 2; +} diff --git a/hexpm/proto/policy.proto b/hexpm/proto/policy.proto new file mode 100644 index 00000000000..3e210007469 --- /dev/null +++ b/hexpm/proto/policy.proto @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2014 The Hex Team + +syntax = "proto2"; + +package policy; + +import "package.proto"; + +message Policy { + // Name of repository + required string repository = 1; + + // Policy name within the repository + // (matches ^[a-z0-9][a-z0-9_\-\.]*[a-z0-9]$, length 3..64) + required string name = 2; + + // Optional, free-form description (admin-set, surfaced in CLI/UI) + optional string description = 3; + + // Whether the policy is publicly readable or restricted to org members. + // Read at the edge to decide whether to enforce auth on the fetch. + // Adding new Visibility values is a breaking change — old clients will + // treat unknown values as PRIVATE per the fail-closed rule. + required Visibility visibility = 4; + + // One entry per repository the policy constrains (in practice "hexpm" and + // the org's own repository). A candidate release is matched to the entry + // whose repository equals the release's repository; a release from a + // repository with no matching entry is unconstrained by this policy. + repeated RepositoryPolicy repositories = 5; +} + +enum Visibility { + // PRIVATE is the safe default; unknown enum values must be treated as PRIVATE. + VISIBILITY_PRIVATE = 0; + VISIBILITY_PUBLIC = 1; +} + +message RepositoryPolicy { + // Repository this entry applies to (e.g. "hexpm" or the org's repository). + required string repository = 1; + + // Baseline limits applied to every release in this repository. Unset = no + // restriction. Restrictions never apply to releases permitted by an ALLOW + // override (those bypass all limits). + optional Restriction restriction = 2; + + // Per-package final say, evaluated against each release in this repository. + // An ALLOW override permits the release immediately and bypasses + // `restriction`; a DENY override blocks it. When multiple overrides match a + // release, the one with the most specific requirement wins. + repeated Override overrides = 3; +} + +message Restriction { + // Advisory limit. If set, deny any release whose maximum advisory severity + // is at least this value. Unset = no advisory limit. + optional package.AdvisorySeverity advisory_min_severity = 1; + + // Retirement limit. If non-empty, deny any release retired with a reason in + // this set. Empty = no retirement limit. + repeated package.RetirementReason retirement_reasons = 2 [packed = true]; + + // Minimum release age. Same duration grammar as the Hex cooldown config + // ("7d", "2w", "1mo", "0"). Unset or "0" = no minimum age. If multiple + // active policies declare cooldowns, the effective cooldown is the strictest. + optional string cooldown = 3; +} + +message PackageRef { + // Package name. + required string package = 1; + + // Optional version requirement (e.g. "~> 1.7"). Unset = the whole package. + optional string requirement = 2; +} + +message Override { + // Whether this override permits or blocks the matching release. + required OverrideAction action = 1; + + // The package (and optional requirement) the override applies to. + required PackageRef ref = 2; +} + +enum OverrideAction { + // Permit the release and bypass `restriction`. + OVERRIDE_ACTION_ALLOW = 0; + // Block the release. + OVERRIDE_ACTION_DENY = 1; +} diff --git a/hexpm/proto/versions.proto b/hexpm/proto/versions.proto index e1e3515545c..eb60c846149 100644 --- a/hexpm/proto/versions.proto +++ b/hexpm/proto/versions.proto @@ -7,18 +7,20 @@ package versions; message Versions { // All packages in the repository - repeated VersionsPackage packages = 1; + repeated Package packages = 1; // Name of repository required string repository = 2; } -message VersionsPackage { +message Package { // Package name required string name = 1; // All released versions of the package repeated string versions = 2; // Zero-based indexes of retired versions in the versions field, see package.proto - repeated int32 retired = 3 [packed=true]; + repeated int32 retired = 3 [packed = true]; // If set, the name of the package repository (NEVER USED, DEPRECATED) // string repository = 4; + // Zero-based indexes of versions with security advisories in the versions field, see package.proto + repeated int32 with_advisories = 5 [packed = true]; } diff --git a/hexpm/src/lib.rs b/hexpm/src/lib.rs index d3e86d5a2b6..10a922cba0f 100644 --- a/hexpm/src/lib.rs +++ b/hexpm/src/lib.rs @@ -281,8 +281,6 @@ pub fn api_unretire_release_response(response: http::Response>) -> Resul /// the package registry. /// /// https://github.com/hexpm/specifications/blob/main/registry-v2.md -/// -/// TODO: Where are the API docs for this? pub fn repository_v2_get_versions_request( credentials: Option<&Credentials>, config: &Config, @@ -395,15 +393,25 @@ pub fn repository_v2_package_parse_body( verify_payload(signed, public_key).map_err(|_| ApiError::IncorrectPayloadSignature)?; let package = proto::package::Package::decode(payload.as_slice())?; - let releases = package - .releases - .clone() + let proto::package::Package { + name, + repository, + advisories: proto_advisories, + releases: proto_releases, + } = package; + + let advisories = proto_advisories .into_iter() - .map(proto_to_release) + .map(proto_to_security_advisory) + .collect::, _>>()?; + let releases = proto_releases + .into_iter() + .map(|release| proto_to_release(release, &advisories)) .collect::, _>>()?; let package = Package { - name: package.name, - repository: package.repository, + name, + repository, + advisories, releases, }; @@ -411,7 +419,6 @@ pub fn repository_v2_package_parse_body( } /// Create a request to download a version of a package as a tarball -/// TODO: Where are the API docs for this? pub fn repository_get_package_tarball_request( name: &str, version: &str, @@ -869,7 +876,10 @@ fn proto_to_dep(dep: proto::package::Dependency) -> Result<(String, Dependency), )) } -fn proto_to_release(release: proto::package::Release) -> Result, ApiError> { +fn proto_to_release( + release: proto::package::Release, + package_advisories: &[SecurityAdvisory], +) -> Result, ApiError> { let dependencies = release .dependencies .clone() @@ -878,23 +888,73 @@ fn proto_to_release(release: proto::package::Release) -> Result, Api .collect::, _>>()?; let version = Version::try_from(release.version.as_str()) .expect("Failed to parse version format from Hex"); + let security_advisories = release + .advisory_indexes + .iter() + .map(|index| package_advisories[*index as usize].clone()) + .collect(); Ok(Release { version, outer_checksum: release.outer_checksum.unwrap_or_default(), retirement_status: proto_to_retirement_status(release.retired), requirements: dependencies, + security_advisories, meta: (), }) } -#[derive(Debug, PartialEq, Eq, Clone)] +fn proto_to_security_advisory( + advisory: proto::package::SecurityAdvisory, +) -> Result { + let proto::package::SecurityAdvisory { + id, + summary, + aliases, + api_url, + cvss_score, + html_url, + severity, + } = advisory; + // We use this instead of just `SecurityAdvisory::severity()` to save `None` + // state, since otherwise it will be lost. + let cvss_rating = severity + .and_then(|severity| severity.try_into().ok()) + .map(proto_to_advisory_severity); + Ok(SecurityAdvisory { + id, + summary, + html_url, + cvss_rating, + cvss_score, + api_url, + aliases, + }) +} + +fn proto_to_advisory_severity(severity: proto::package::AdvisorySeverity) -> AdvisorySeverity { + use proto::package::AdvisorySeverity::*; + match severity { + SeverityNone => AdvisorySeverity::None, + SeverityLow => AdvisorySeverity::Low, + SeverityMedium => AdvisorySeverity::Medium, + SeverityHigh => AdvisorySeverity::High, + SeverityCritical => AdvisorySeverity::Critical, + } +} + +#[derive(Debug, PartialEq, Clone)] pub struct Package { + /// Name of package pub name: String, + /// Name of repository pub repository: String, + /// All releases of the package pub releases: Vec>, + /// All security advisories affecting any release of the package + pub advisories: Vec, } -#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] +#[derive(Debug, PartialEq, Clone, serde::Deserialize)] pub struct Release { /// Release version pub version: Version, @@ -907,6 +967,8 @@ pub struct Release { /// required when encoding but optional when decoding #[serde(alias = "checksum", deserialize_with = "deserialize_checksum")] pub outer_checksum: Vec, + /// Security advisories affecting this release of the package + pub security_advisories: Vec, /// This is not present in all API endpoints so may be absent sometimes. pub meta: Meta, } @@ -975,6 +1037,63 @@ impl RetirementReason { } } +#[derive(Debug, PartialEq, Clone, serde::Deserialize)] +pub struct SecurityAdvisory { + /// Advisory identifier (e.g. GHSA-xxxx-xxxx-xxxx or CVE-xxxx-xxxxx) + pub id: String, + /// Short description of the advisory + pub summary: String, + /// OSV web URL for the advisory + pub html_url: String, + /// Severity of the advisory + pub cvss_rating: Option, + /// CVSS score (0.0–10.0) + pub cvss_score: Option, + /// OSV API URL for the advisory + pub api_url: String, + /// Other identifiers for the same vulnerability (e.g. a CVE id when the + /// primary id is a GHSA id, or vice versa). + pub aliases: Vec, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum AdvisorySeverity { + None, + Low, + Medium, + High, + Critical, +} + +impl<'de> serde::Deserialize<'de> for AdvisorySeverity { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: &str = serde::de::Deserialize::deserialize(deserializer)?; + match s { + "none" => Ok(AdvisorySeverity::None), + "low" => Ok(AdvisorySeverity::Low), + "medium" => Ok(AdvisorySeverity::Medium), + "high" => Ok(AdvisorySeverity::High), + "critical" => Ok(AdvisorySeverity::Critical), + _ => Err(serde::de::Error::custom("unknown advisory severity type")), + } + } +} + +impl AdvisorySeverity { + pub fn to_str(&self) -> &'static str { + match self { + AdvisorySeverity::None => "none", + AdvisorySeverity::Low => "low", + AdvisorySeverity::Medium => "medium", + AdvisorySeverity::High => "high", + AdvisorySeverity::Critical => "critical", + } + } +} + #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] pub struct Dependency { /// Version requirement of dependency diff --git a/hexpm/src/proto/names.rs b/hexpm/src/proto/names.rs new file mode 100644 index 00000000000..83b870d40b6 --- /dev/null +++ b/hexpm/src/proto/names.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2026 The Gleam contributors + +// This file is @generated by prost-build. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Names { + /// All packages in the repository + #[prost(message, repeated, tag = "1")] + pub packages: ::prost::alloc::vec::Vec, + /// Name of repository + #[prost(string, required, tag = "2")] + pub repository: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Package { + /// Package name + #[prost(string, required, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub updated_at: ::core::option::Option, +} +/// Based on google.protobuf.Timestamp +/// +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Timestamp { + #[prost(int64, required, tag = "1")] + pub seconds: i64, + #[prost(int32, required, tag = "2")] + pub nanos: i32, +} diff --git a/hexpm/src/proto/package.rs b/hexpm/src/proto/package.rs index af24f9008a9..a664ea0b9e3 100644 --- a/hexpm/src/proto/package.rs +++ b/hexpm/src/proto/package.rs @@ -13,6 +13,9 @@ pub struct Package { /// Name of repository #[prost(string, required, tag = "3")] pub repository: ::prost::alloc::string::String, + /// All security advisories affecting any release of the package + #[prost(message, repeated, tag = "4")] + pub advisories: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Release { @@ -34,8 +37,15 @@ pub struct Release { /// required when encoding but optional when decoding #[prost(bytes = "vec", optional, tag = "5")] pub outer_checksum: ::core::option::Option<::prost::alloc::vec::Vec>, + /// Indexes into Package.advisories for advisories affecting this release + #[prost(uint32, repeated, packed = "false", tag = "6")] + pub advisory_indexes: ::prost::alloc::vec::Vec, + /// Release published timestamp. Optional for backwards compatibility — + /// clients treat absence as "no information". + #[prost(message, optional, tag = "7")] + pub published_at: ::core::option::Option, } -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct RetirementStatus { #[prost(enumeration = "RetirementReason", required, tag = "1")] pub reason: i32, @@ -43,6 +53,31 @@ pub struct RetirementStatus { pub message: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SecurityAdvisory { + /// Advisory identifier (e.g. GHSA-xxxx-xxxx-xxxx or CVE-xxxx-xxxxx) + #[prost(string, required, tag = "1")] + pub id: ::prost::alloc::string::String, + /// Short description of the advisory + #[prost(string, required, tag = "2")] + pub summary: ::prost::alloc::string::String, + /// OSV web URL for the advisory + #[prost(string, required, tag = "3")] + pub html_url: ::prost::alloc::string::String, + /// Severity of the advisory + #[prost(enumeration = "AdvisorySeverity", optional, tag = "4")] + pub severity: ::core::option::Option, + /// CVSS score (0.0–10.0) + #[prost(float, optional, tag = "5")] + pub cvss_score: ::core::option::Option, + /// OSV API URL for the advisory + #[prost(string, required, tag = "6")] + pub api_url: ::prost::alloc::string::String, + /// Other identifiers for the same vulnerability (e.g. a CVE id when the + /// primary id is a GHSA id, or vice versa). + #[prost(string, repeated, tag = "7")] + pub aliases: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Dependency { /// Package name of dependency #[prost(string, required, tag = "1")] @@ -61,6 +96,15 @@ pub struct Dependency { #[prost(string, optional, tag = "5")] pub repository: ::core::option::Option<::prost::alloc::string::String>, } +/// Based on google.protobuf.Timestamp +/// +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Timestamp { + #[prost(int64, required, tag = "1")] + pub seconds: i64, + #[prost(int32, required, tag = "2")] + pub nanos: i32, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum RetirementReason { @@ -96,3 +140,38 @@ impl RetirementReason { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum AdvisorySeverity { + SeverityNone = 0, + SeverityLow = 1, + SeverityMedium = 2, + SeverityHigh = 3, + SeverityCritical = 4, +} +impl AdvisorySeverity { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::SeverityNone => "SEVERITY_NONE", + Self::SeverityLow => "SEVERITY_LOW", + Self::SeverityMedium => "SEVERITY_MEDIUM", + Self::SeverityHigh => "SEVERITY_HIGH", + Self::SeverityCritical => "SEVERITY_CRITICAL", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SEVERITY_NONE" => Some(Self::SeverityNone), + "SEVERITY_LOW" => Some(Self::SeverityLow), + "SEVERITY_MEDIUM" => Some(Self::SeverityMedium), + "SEVERITY_HIGH" => Some(Self::SeverityHigh), + "SEVERITY_CRITICAL" => Some(Self::SeverityCritical), + _ => None, + } + } +} diff --git a/hexpm/src/proto/policy.rs b/hexpm/src/proto/policy.rs new file mode 100644 index 00000000000..b528bf73950 --- /dev/null +++ b/hexpm/src/proto/policy.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2026 The Gleam contributors + +// This file is @generated by prost-build. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Policy { + /// Name of repository + #[prost(string, required, tag = "1")] + pub repository: ::prost::alloc::string::String, + /// Policy name within the repository + /// (matches ^[a-z0-9][a-z0-9_\-\.]*\[a-z0-9\]$, length 3..64) + #[prost(string, required, tag = "2")] + pub name: ::prost::alloc::string::String, + /// Optional, free-form description (admin-set, surfaced in CLI/UI) + #[prost(string, optional, tag = "3")] + pub description: ::core::option::Option<::prost::alloc::string::String>, + /// Whether the policy is publicly readable or restricted to org members. + /// Read at the edge to decide whether to enforce auth on the fetch. + /// Adding new Visibility values is a breaking change — old clients will + /// treat unknown values as PRIVATE per the fail-closed rule. + #[prost(enumeration = "Visibility", required, tag = "4")] + pub visibility: i32, + /// One entry per repository the policy constrains (in practice "hexpm" and + /// the org's own repository). A candidate release is matched to the entry + /// whose repository equals the release's repository; a release from a + /// repository with no matching entry is unconstrained by this policy. + #[prost(message, repeated, tag = "5")] + pub repositories: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryPolicy { + /// Repository this entry applies to (e.g. "hexpm" or the org's repository). + #[prost(string, required, tag = "1")] + pub repository: ::prost::alloc::string::String, + /// Baseline limits applied to every release in this repository. Unset = no + /// restriction. Restrictions never apply to releases permitted by an ALLOW + /// override (those bypass all limits). + #[prost(message, optional, tag = "2")] + pub restriction: ::core::option::Option, + /// Per-package final say, evaluated against each release in this repository. + /// An ALLOW override permits the release immediately and bypasses + /// `restriction`; a DENY override blocks it. When multiple overrides match a + /// release, the one with the most specific requirement wins. + #[prost(message, repeated, tag = "3")] + pub overrides: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Restriction { + /// Advisory limit. If set, deny any release whose maximum advisory severity + /// is at least this value. Unset = no advisory limit. + #[prost(enumeration = "super::package::AdvisorySeverity", optional, tag = "1")] + pub advisory_min_severity: ::core::option::Option, + /// Retirement limit. If non-empty, deny any release retired with a reason in + /// this set. Empty = no retirement limit. + #[prost(enumeration = "super::package::RetirementReason", repeated, tag = "2")] + pub retirement_reasons: ::prost::alloc::vec::Vec, + /// Minimum release age. Same duration grammar as the Hex cooldown config + /// ("7d", "2w", "1mo", "0"). Unset or "0" = no minimum age. If multiple + /// active policies declare cooldowns, the effective cooldown is the strictest. + #[prost(string, optional, tag = "3")] + pub cooldown: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PackageRef { + /// Package name. + #[prost(string, required, tag = "1")] + pub package: ::prost::alloc::string::String, + /// Optional version requirement (e.g. "~> 1.7"). Unset = the whole package. + #[prost(string, optional, tag = "2")] + pub requirement: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Override { + /// Whether this override permits or blocks the matching release. + #[prost(enumeration = "OverrideAction", required, tag = "1")] + pub action: i32, + /// The package (and optional requirement) the override applies to. + #[prost(message, required, tag = "2")] + pub r#ref: PackageRef, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum Visibility { + /// PRIVATE is the safe default; unknown enum values must be treated as PRIVATE. + Private = 0, + Public = 1, +} +impl Visibility { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Private => "VISIBILITY_PRIVATE", + Self::Public => "VISIBILITY_PUBLIC", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "VISIBILITY_PRIVATE" => Some(Self::Private), + "VISIBILITY_PUBLIC" => Some(Self::Public), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum OverrideAction { + /// Permit the release and bypass `restriction`. + Allow = 0, + /// Block the release. + Deny = 1, +} +impl OverrideAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Allow => "OVERRIDE_ACTION_ALLOW", + Self::Deny => "OVERRIDE_ACTION_DENY", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "OVERRIDE_ACTION_ALLOW" => Some(Self::Allow), + "OVERRIDE_ACTION_DENY" => Some(Self::Deny), + _ => None, + } + } +} diff --git a/hexpm/src/proto/signed.rs b/hexpm/src/proto/signed.rs index d9edf35ad2b..10c5ea69473 100644 --- a/hexpm/src/proto/signed.rs +++ b/hexpm/src/proto/signed.rs @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2026 The Gleam contributors // This file is @generated by prost-build. -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Signed { /// Signed contents #[prost(bytes = "vec", required, tag = "1")] diff --git a/hexpm/src/proto/versions.rs b/hexpm/src/proto/versions.rs index b32308bd13e..0bf59580736 100644 --- a/hexpm/src/proto/versions.rs +++ b/hexpm/src/proto/versions.rs @@ -6,13 +6,13 @@ pub struct Versions { /// All packages in the repository #[prost(message, repeated, tag = "1")] - pub packages: ::prost::alloc::vec::Vec, + pub packages: ::prost::alloc::vec::Vec, /// Name of repository #[prost(string, required, tag = "2")] pub repository: ::prost::alloc::string::String, } -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct VersionsPackage { +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Package { /// Package name #[prost(string, required, tag = "1")] pub name: ::prost::alloc::string::String, @@ -20,9 +20,11 @@ pub struct VersionsPackage { #[prost(string, repeated, tag = "2")] pub versions: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, /// Zero-based indexes of retired versions in the versions field, see package.proto - /// - /// If set, the name of the package repository (NEVER USED, DEPRECATED) - /// string repository = 4; #[prost(int32, repeated, tag = "3")] pub retired: ::prost::alloc::vec::Vec, + /// If set, the name of the package repository (NEVER USED, DEPRECATED) + /// string repository = 4; + /// Zero-based indexes of versions with security advisories in the versions field, see package.proto + #[prost(int32, repeated, tag = "5")] + pub with_advisories: ::prost::alloc::vec::Vec, } diff --git a/hexpm/src/tests.rs b/hexpm/src/tests.rs index cf1f588f5b9..30881823f38 100644 --- a/hexpm/src/tests.rs +++ b/hexpm/src/tests.rs @@ -450,6 +450,7 @@ fn expected_package_exfmt() -> Package { Package { name: "exfmt".to_string(), repository: "hexpm".to_string(), + advisories: vec![], releases: vec![ Release { version: Version::try_from("0.0.0").unwrap(), @@ -459,6 +460,7 @@ fn expected_package_exfmt() -> Package { 82, 48, 191, 145, 92, 172, 0, 108, 238, 71, 57, 23, 101, 177, 161, 83, 91, 182, 18, 232, 249, 225, 29, 12, 246, 5, 215, 165, 32, 57, 179, 110, ], + security_advisories: vec![], meta: (), }, Release { @@ -469,6 +471,7 @@ fn expected_package_exfmt() -> Package { 111, 246, 240, 176, 118, 229, 12, 15, 164, 61, 186, 3, 89, 106, 153, 225, 247, 52, 245, 8, 216, 139, 21, 232, 200, 16, 214, 59, 241, 188, 9, 6, ], + security_advisories: vec![], meta: (), }, Release { @@ -479,6 +482,7 @@ fn expected_package_exfmt() -> Package { 149, 9, 192, 229, 84, 162, 110, 207, 161, 43, 31, 0, 126, 168, 14, 243, 31, 43, 195, 238, 100, 91, 78, 100, 213, 181, 101, 154, 106, 168, 170, 107, ], + security_advisories: vec![], meta: (), }, Release { @@ -489,6 +493,7 @@ fn expected_package_exfmt() -> Package { 157, 229, 28, 212, 92, 249, 14, 240, 235, 104, 31, 12, 160, 199, 83, 195, 154, 105, 222, 37, 221, 80, 181, 183, 113, 240, 234, 107, 144, 85, 255, 65, ], + security_advisories: vec![], meta: (), }, Release { @@ -499,6 +504,7 @@ fn expected_package_exfmt() -> Package { 112, 250, 133, 189, 183, 192, 54, 218, 115, 55, 216, 97, 204, 201, 191, 168, 250, 133, 138, 252, 202, 240, 74, 197, 228, 235, 81, 18, 241, 7, 155, 38, ], + security_advisories: vec![], meta: (), }, Release { @@ -509,6 +515,7 @@ fn expected_package_exfmt() -> Package { 131, 20, 29, 160, 171, 124, 7, 125, 210, 88, 17, 189, 199, 49, 191, 190, 14, 162, 38, 247, 52, 176, 189, 17, 7, 188, 151, 152, 24, 64, 170, 29, ], + security_advisories: vec![], meta: (), }, Release { @@ -519,6 +526,7 @@ fn expected_package_exfmt() -> Package { 109, 162, 185, 169, 26, 4, 62, 60, 167, 54, 182, 161, 140, 197, 75, 113, 183, 117, 247, 201, 218, 228, 14, 160, 115, 157, 196, 51, 108, 16, 96, 217, ], + security_advisories: vec![], meta: (), }, Release { @@ -529,6 +537,7 @@ fn expected_package_exfmt() -> Package { 97, 50, 95, 212, 242, 59, 245, 177, 140, 78, 79, 180, 108, 174, 119, 176, 24, 80, 218, 152, 178, 227, 152, 242, 32, 126, 72, 67, 222, 0, 173, 170, ], + security_advisories: vec![], meta: (), }, Release { @@ -539,6 +548,7 @@ fn expected_package_exfmt() -> Package { 246, 178, 237, 214, 217, 158, 143, 52, 130, 186, 64, 50, 94, 175, 161, 81, 68, 186, 4, 73, 53, 226, 235, 144, 209, 84, 231, 136, 165, 119, 122, 126, ], + security_advisories: vec![], meta: (), }, Release { @@ -549,6 +559,7 @@ fn expected_package_exfmt() -> Package { 151, 86, 157, 218, 218, 131, 240, 119, 198, 216, 202, 240, 65, 17, 57, 228, 84, 252, 59, 207, 246, 49, 22, 21, 52, 47, 51, 139, 190, 9, 95, 109, ], + security_advisories: vec![], meta: (), }, ], @@ -891,6 +902,7 @@ fn get_package_release_response_ok() { "app": "cowboy" } }, + "security_advisories": [], "meta": { "app": "clint", "build_tools": ["mix"] @@ -903,6 +915,7 @@ fn get_package_release_response_ok() { resp, Release { version: Version::new(0, 0, 1), + security_advisories: vec![], requirements: [ ( "plug".into(),