feat: support composite actions in use-trusted-publishing (#899)

This commit is contained in:
William Woodruff 2025-06-05 17:00:20 -04:00 committed by GitHub
parent ad62fd42c6
commit a1252c260c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 127 additions and 53 deletions

View file

@ -1,15 +1,10 @@
use std::ops::Deref;
use github_actions_models::{
common::{EnvValue, Uses},
workflow::job::StepBody,
};
use github_actions_models::common::{EnvValue, Uses};
use indexmap::IndexMap;
use super::{Audit, AuditLoadError, audit_meta};
use crate::{
finding::{Confidence, Severity, location::Locatable as _},
models::uses::RepositoryUsesExt as _,
finding::{Confidence, Finding, Severity},
models::{StepBodyCommon, StepCommon, uses::RepositoryUsesExt as _},
state::AuditState,
};
@ -30,6 +25,58 @@ audit_meta!(
);
impl UseTrustedPublishing {
fn process_step<'doc>(
&self,
step: &impl StepCommon<'doc>,
) -> anyhow::Result<Vec<Finding<'doc>>> {
let mut findings = vec![];
let StepBodyCommon::Uses {
uses: Uses::Repository(uses),
with,
} = &step.body()
else {
return Ok(findings);
};
let candidate = Self::finding()
.severity(Severity::Informational)
.confidence(Confidence::High)
.add_location(
step.location()
.primary()
.with_keys(&["uses".into()])
.annotated("this step"),
);
if uses.matches("pypa/gh-action-pypi-publish")
&& self.pypi_publish_uses_manual_credentials(with)
{
findings.push(
candidate
.add_location(
step.location()
.primary()
.with_keys(&["with".into(), "password".into()])
.annotated(USES_MANUAL_CREDENTIAL),
)
.build(step)?,
);
} else if ((uses.matches("rubygems/release-gem"))
&& self.release_gem_uses_manual_credentials(with))
|| (uses.matches("rubygems/configure-rubygems-credential")
&& self.rubygems_credential_uses_manual_credentials(with))
{
findings.push(
candidate
.add_location(step.location().primary().annotated(USES_MANUAL_CREDENTIAL))
.build(step)?,
);
}
Ok(findings)
}
fn pypi_publish_uses_manual_credentials(&self, with: &IndexMap<String, EnvValue>) -> bool {
// `password` implies the step isn't using Trusted Publishing,
// but we also need to check `repository-url` to prevent false-positives
@ -75,51 +122,13 @@ impl Audit for UseTrustedPublishing {
&self,
step: &super::Step<'doc>,
) -> anyhow::Result<Vec<super::Finding<'doc>>> {
let mut findings = vec![];
self.process_step(step)
}
let StepBody::Uses {
uses: Uses::Repository(uses),
with,
} = &step.deref().body
else {
return Ok(findings);
};
let candidate = Self::finding()
.severity(Severity::Informational)
.confidence(Confidence::High)
.add_location(
step.location()
.primary()
.with_keys(&["uses".into()])
.annotated("this step"),
);
if uses.matches("pypa/gh-action-pypi-publish")
&& self.pypi_publish_uses_manual_credentials(with)
{
findings.push(
candidate
.add_location(
step.location()
.primary()
.with_keys(&["with".into(), "password".into()])
.annotated(USES_MANUAL_CREDENTIAL),
)
.build(step.workflow())?,
);
} else if ((uses.matches("rubygems/release-gem"))
&& self.release_gem_uses_manual_credentials(with))
|| (uses.matches("rubygems/configure-rubygems-credential")
&& self.rubygems_credential_uses_manual_credentials(with))
{
findings.push(
candidate
.add_location(step.location().primary().annotated(USES_MANUAL_CREDENTIAL))
.build(step.workflow())?,
);
}
Ok(findings)
fn audit_composite_step<'doc>(
&self,
step: &crate::models::CompositeStep<'doc>,
) -> anyhow::Result<Vec<Finding<'doc>>> {
self.process_step(step)
}
}

View file

@ -249,6 +249,25 @@ fn unpinned_uses() -> Result<()> {
Ok(())
}
#[test]
fn use_trusted_publishing() -> Result<()> {
insta::assert_snapshot!(
zizmor()
.input(input_under_test("use-trusted-publishing.yml"))
.run()?
);
insta::assert_snapshot!(
zizmor()
.input(input_under_test(
"use-trusted-publishing/demo-action/action.yml"
))
.run()?
);
Ok(())
}
#[test]
fn insecure_commands() -> Result<()> {
insta::assert_snapshot!(

View file

@ -0,0 +1,16 @@
---
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"use-trusted-publishing/demo-action/action.yml\")).run()?"
---
info[use-trusted-publishing]: prefer trusted publishing for authentication
--> @@INPUT@@:9:7
|
9 | - uses: pypa/gh-action-pypi-publish@release/v1 # zizmor: ignore[unpinned-uses]
| -------------------------------------------- info: this step
10 | with:
11 | password: ${{ secrets.PYPI_TOKEN }}
| ----------------------------------- info: uses a manually-configured credential instead of Trusted Publishing
|
= note: audit confidence → High
2 findings (1 ignored): 0 unknown, 1 informational, 0 low, 0 medium, 0 high

View file

@ -0,0 +1,16 @@
---
source: crates/zizmor/tests/integration/snapshot.rs
expression: "zizmor().input(input_under_test(\"use-trusted-publishing.yml\")).run()?"
---
info[use-trusted-publishing]: prefer trusted publishing for authentication
--> @@INPUT@@:13:9
|
13 | uses: pypa/gh-action-pypi-publish@release/v1 # zizmor: ignore[unpinned-uses]
| -------------------------------------------- info: this step
14 | with:
15 | password: ${{ secrets.PYPI_TOKEN }}
| ----------------------------------- info: uses a manually-configured credential instead of Trusted Publishing
|
= note: audit confidence → High
3 findings (1 ignored, 1 suppressed): 0 unknown, 1 informational, 0 low, 0 medium, 0 high

View file

@ -0,0 +1,11 @@
# demo of a composite action being flagged by use-trusted-publishing
name: use-trusted-publishing-composite-action
description: use-trusted-publishing-composite-action
runs:
using: composite
steps:
- uses: pypa/gh-action-pypi-publish@release/v1 # zizmor: ignore[unpinned-uses]
with:
password: ${{ secrets.PYPI_TOKEN }}

View file

@ -13,6 +13,8 @@ of `zizmor`.
* The [artipacked] audit now produces findings on composite action definitions,
rather than just workflow definitions (#896)
* The [use-trusted-publishing] audit now produces findings on composite
action definitions, rather than just workflow definitions (#899)
### Bug Fixes 🐛
@ -841,3 +843,4 @@ This is one of `zizmor`'s bigger recent releases! Key enhancements include:
[unsound-contains]: ./audits.md#unsound-contains
[unpinned-images]: ./audits.md#unpinned-images
[insecure-commands]: ./audits.md#insecure-commands
[use-trusted-publishing]: ./audits.md#use-trusted-publishing