Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 12 additions & 0 deletions e2e/cli/test_tool
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,15 @@ assert_fail "mise tool INVALID_TOOL_NAME"
mise use tiny@1.0.0
mise use tiny@1.1.0
assert "mise tool tiny --installed" "1.0.0 1.1.0"

# Test security field exists in JSON output for aqua tool
mise tool aqua:cli/cli --json | jq -e '.security' || fail "security field not found in JSON"

# Test security field is an array
mise tool aqua:cli/cli --json | jq -e '.security | type == "array"' || fail "security is not an array"
Comment on lines +23 to +26

Copilot AI Dec 15, 2025

Copy link

Choose a reason for hiding this comment

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

The tool name 'aqua:cli/cli' is repeated three times. Consider storing it in a variable to improve maintainability and make it easier to change the test tool if needed.

Copilot uses AI. Check for mistakes.

# Test non-aqua tool has empty security array
mise tool node --json | jq -e '.security == []' || fail "non-aqua tool should have empty security"

# Test that security features have type field when present
mise tool aqua:cli/cli --json | jq -e '.security | if length > 0 then .[0].type else true end' || fail "security feature missing type field"

Copilot AI Dec 15, 2025

Copy link

Choose a reason for hiding this comment

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

The tool name 'aqua:cli/cli' is repeated three times. Consider storing it in a variable to improve maintainability and make it easier to change the test tool if needed.

Copilot uses AI. Check for mistakes.
56 changes: 56 additions & 0 deletions src/backend/aqua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,62 @@ impl Backend for AquaBackend {
.and_then(|p| p.description.clone())
}

async fn security_info(&self) -> Vec<crate::backend::SecurityFeature> {
use crate::backend::SecurityFeature;

let pkg = match AQUA_REGISTRY.package(&self.ba.tool_name).await {
Ok(pkg) => pkg,
Err(_) => return vec![],
};

let mut features = vec![];

// Checksum
if let Some(checksum) = &pkg.checksum {
if checksum.enabled() {
features.push(SecurityFeature::Checksum {
algorithm: checksum.algorithm.as_ref().map(|a| a.to_string()),
});
}
}

// GitHub Attestations
if let Some(attestations) = &pkg.github_artifact_attestations {
if attestations.enabled.unwrap_or(false) {
features.push(SecurityFeature::GithubAttestations {
signer_workflow: attestations.signer_workflow.clone(),
});
}
}

// SLSA
if let Some(slsa) = &pkg.slsa_provenance {
if slsa.enabled.unwrap_or(false) {
features.push(SecurityFeature::Slsa);
}
}

// Cosign (nested in checksum)
if let Some(checksum) = &pkg.checksum {
if let Some(cosign) = &checksum.cosign {
if cosign.enabled.unwrap_or(false) {
features.push(SecurityFeature::Cosign);
}
}
}

// Minisign
if let Some(minisign) = &pkg.minisign {
if minisign.enabled.unwrap_or(false) {
features.push(SecurityFeature::Minisign {
public_key: minisign.public_key.clone(),
});
}
}

features
}

fn ba(&self) -> &Arc<BackendArg> {
&self.ba
}
Expand Down
23 changes: 23 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ impl VersionInfo {
}
}

/// Security feature information for a tool
#[derive(Debug, Clone, serde::Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum SecurityFeature {
Checksum {
#[serde(skip_serializing_if = "Option::is_none")]
algorithm: Option<String>,
},
GithubAttestations {
#[serde(skip_serializing_if = "Option::is_none")]
signer_workflow: Option<String>,
},
Slsa,
Cosign,
Minisign {
#[serde(skip_serializing_if = "Option::is_none")]
public_key: Option<String>,
},
}

static TOOLS: Mutex<Option<Arc<BackendMap>>> = Mutex::new(None);

pub async fn load_tools() -> Result<Arc<BackendMap>> {
Expand Down Expand Up @@ -295,6 +315,9 @@ pub trait Backend: Debug + Send + Sync {
async fn description(&self) -> Option<String> {
None
}
async fn security_info(&self) -> Vec<SecurityFeature> {
vec![]
}
fn get_plugin_type(&self) -> Option<PluginType> {
None
}
Expand Down
33 changes: 30 additions & 3 deletions src/cli/tool.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::backend::SecurityFeature;
use crate::ui::style;
use eyre::Result;
use itertools::Itertools;
Expand Down Expand Up @@ -75,10 +76,10 @@ impl Tool {
}
};

let description = if let Some(backend) = backend {
backend.description().await
let (description, security) = if let Some(backend) = &backend {
(backend.description().await, backend.security_info().await)
} else {
None
(None, vec![])
};
let info = ToolInfo {
backend: ba.full(),
Expand All @@ -105,6 +106,7 @@ impl Tool {
}),
config_source: tvl.map(|tvl| tvl.source.clone()),
tool_options: ba.opts(),
security,
};

if self.json {
Expand Down Expand Up @@ -242,6 +244,30 @@ impl Tool {
.join(","),
));
}
if info.security.is_empty() {
table.push(("Security:", "[none]".to_string()));
} else {
let security_str = info
.security
.iter()
.map(|f| match f {
SecurityFeature::Checksum { algorithm } => {
if let Some(alg) = algorithm {
format!("checksum ({})", alg)
} else {
"checksum".to_string()
}
}
SecurityFeature::GithubAttestations { .. } => {
"github_attestations".to_string()
}
SecurityFeature::Slsa => "slsa".to_string(),
SecurityFeature::Cosign => "cosign".to_string(),
SecurityFeature::Minisign { .. } => "minisign".to_string(),
})
.join(", ");
table.push(("Security:", security_str));
}
let mut table = tabled::Table::new(table);
table::default_style(&mut table, true);
miseprintln!("{table}");
Expand All @@ -260,6 +286,7 @@ struct ToolInfo {
active_versions: Option<Vec<String>>,
config_source: Option<ToolSource>,
tool_options: ToolVersionOptions,
security: Vec<SecurityFeature>,
}

static AFTER_LONG_HELP: &str = color_print::cstr!(
Expand Down
Loading