Skip to content

Commit

Permalink
refactor: add 'primary' locations (#328)
Browse files Browse the repository at this point in the history
Signed-off-by: William Woodruff <william@yossarian.net>
  • Loading branch information
woodruffw authored Dec 19, 2024
1 parent 0d786b8 commit f2f787f
Show file tree
Hide file tree
Showing 16 changed files with 75 additions and 16 deletions.
2 changes: 2 additions & 0 deletions src/audit/artipacked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ impl WorkflowAudit for Artipacked {
.add_location(
checkout
.location()
.primary()
.annotated("does not set persist-credentials: false"),
)
.build(workflow)?,
Expand All @@ -127,6 +128,7 @@ impl WorkflowAudit for Artipacked {
.add_location(
checkout
.location()
.primary()
.annotated("does not set persist-credentials: false"),
)
.add_location(
Expand Down
2 changes: 2 additions & 0 deletions src/audit/dangerous_triggers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl WorkflowAudit for DangerousTriggers {
.add_location(
workflow
.location()
.primary()
.with_keys(&["on".into()])
.annotated("pull_request_target is almost always used insecurely"),
)
Expand All @@ -42,6 +43,7 @@ impl WorkflowAudit for DangerousTriggers {
.add_location(
workflow
.location()
.primary()
.with_keys(&["on".into()])
.annotated("workflow_run is almost always used insecurely"),
)
Expand Down
2 changes: 2 additions & 0 deletions src/audit/excessive_permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl WorkflowAudit for ExcessivePermissions {
.add_location(
workflow
.location()
.primary()
.with_keys(&["permissions".into()])
.annotated(note),
)
Expand All @@ -86,6 +87,7 @@ impl WorkflowAudit for ExcessivePermissions {
.confidence(confidence)
.add_location(
job.location()
.primary()
.with_keys(&["permissions".into()])
.annotated(note),
)
Expand Down
1 change: 1 addition & 0 deletions src/audit/github_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ impl WorkflowAudit for GitHubEnv {
.confidence(Confidence::Low)
.add_location(
step.location()
.primary()
.with_keys(&["run".into()])
.annotated("GITHUB_ENV write may allow code execution"),
)
Expand Down
2 changes: 2 additions & 0 deletions src/audit/hardcoded_container_credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ impl WorkflowAudit for HardcodedContainerCredentials {
.confidence(Confidence::High)
.add_location(
job.location()
.primary()
.with_keys(&["container".into(), "credentials".into()])
.annotated("container registry password is hard-coded"),
)
Expand All @@ -85,6 +86,7 @@ impl WorkflowAudit for HardcodedContainerCredentials {
.confidence(Confidence::High)
.add_location(
job.location()
.primary()
.with_keys(&[
"services".into(),
service.as_str().into(),
Expand Down
8 changes: 6 additions & 2 deletions src/audit/impostor_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ impl WorkflowAudit for ImpostorCommit {
Self::finding()
.severity(Severity::High)
.confidence(Confidence::High)
.add_location(step.location().annotated(IMPOSTOR_ANNOTATION))
.add_location(
step.location().primary().annotated(IMPOSTOR_ANNOTATION),
)
.build(workflow)?,
);
}
Expand All @@ -159,7 +161,9 @@ impl WorkflowAudit for ImpostorCommit {
Self::finding()
.severity(Severity::High)
.confidence(Confidence::High)
.add_location(job.location().annotated(IMPOSTOR_ANNOTATION))
.add_location(
job.location().primary().annotated(IMPOSTOR_ANNOTATION),
)
.build(workflow)?,
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/audit/insecure_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl InsecureCommands {
.severity(Severity::High)
.persona(Persona::Auditor)
.add_location(
location.with_keys(&["env".into()]).annotated(
location.primary().with_keys(&["env".into()]).annotated(
"non-static environment may contain ACTIONS_ALLOW_UNSECURE_COMMANDS",
),
)
Expand All @@ -47,6 +47,7 @@ impl InsecureCommands {
.severity(Severity::High)
.add_location(
location
.primary()
.with_keys(&["env".into()])
.annotated("insecure commands enabled here"),
)
Expand Down
1 change: 1 addition & 0 deletions src/audit/known_vulnerable_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ impl WorkflowAudit for KnownVulnerableActions {
.severity(severity)
.add_location(
step.location()
.primary()
.with_keys(&["uses".into()])
.annotated(&id)
.with_url(format!("https://github.com/advisories/{id}")),
Expand Down
5 changes: 4 additions & 1 deletion src/audit/ref_confusion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ impl WorkflowAudit for RefConfusion {
.confidence(Confidence::High)
.add_location(
step.location()
.primary()
.with_keys(&["uses".into()])
.annotated(REF_CONFUSION_ANNOTATION),
)
Expand All @@ -102,7 +103,9 @@ impl WorkflowAudit for RefConfusion {
Self::finding()
.severity(Severity::Medium)
.confidence(Confidence::High)
.add_location(job.location().annotated(REF_CONFUSION_ANNOTATION))
.add_location(
job.location().primary().annotated(REF_CONFUSION_ANNOTATION),
)
.build(workflow)?,
)
}
Expand Down
20 changes: 14 additions & 6 deletions src/audit/self_hosted_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ impl WorkflowAudit for SelfHostedRunner {
.persona(Persona::Auditor)
.add_location(
job.location()
.primary()
.with_keys(&["runs-on".into()])
.annotated("self-hosted runner used here"),
)
Expand All @@ -78,9 +79,12 @@ impl WorkflowAudit for SelfHostedRunner {
.severity(Severity::Unknown)
.persona(Persona::Auditor)
.add_location(
job.location().with_keys(&["runs-on".into()]).annotated(
"expression may expand into a self-hosted runner",
),
job.location()
.primary()
.with_keys(&["runs-on".into()])
.annotated(
"expression may expand into a self-hosted runner",
),
)
.build(workflow)?,
);
Expand All @@ -99,6 +103,7 @@ impl WorkflowAudit for SelfHostedRunner {
.persona(Persona::Auditor)
.add_location(
job.location()
.primary()
.with_keys(&["runs-on".into()])
.annotated("runner group implies self-hosted runner"),
)
Expand Down Expand Up @@ -129,9 +134,12 @@ impl WorkflowAudit for SelfHostedRunner {
.annotated("matrix declares self-hosted runner"),
)
.add_location(
job.location().with_keys(&["runs-on".into()]).annotated(
"expression may expand into a self-hosted runner",
),
job.location()
.primary()
.with_keys(&["runs-on".into()])
.annotated(
"expression may expand into a self-hosted runner",
),
)
.build(workflow)?,
)
Expand Down
2 changes: 1 addition & 1 deletion src/audit/template_injection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ impl WorkflowAudit for TemplateInjection {
.persona(persona)
.add_location(step.location_with_name())
.add_location(
script_loc.clone().annotated(format!(
script_loc.clone().primary().annotated(format!(
"{expr} may expand into attacker-controllable code"
)),
)
Expand Down
1 change: 1 addition & 0 deletions src/audit/unpinned_uses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl WorkflowAudit for UnpinnedUses {
.persona(persona)
.add_location(
step.location()
.primary()
.with_keys(&["uses".into()])
.annotated(annotation),
)
Expand Down
4 changes: 3 additions & 1 deletion src/audit/use_trusted_publishing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ impl WorkflowAudit for UseTrustedPublishing {
.confidence(Confidence::High)
.add_location(
step.location()
.primary()
.with_keys(&["uses".into()])
.annotated("this step"),
);
Expand All @@ -98,6 +99,7 @@ impl WorkflowAudit for UseTrustedPublishing {
candidate
.add_location(
step.location()
.primary()
.with_keys(&["with".into(), "password".into()])
.annotated(USES_MANUAL_CREDENTIAL),
)
Expand All @@ -110,7 +112,7 @@ impl WorkflowAudit for UseTrustedPublishing {
{
findings.push(
candidate
.add_location(step.location().annotated(USES_MANUAL_CREDENTIAL))
.add_location(step.location().primary().annotated(USES_MANUAL_CREDENTIAL))
.build(step.workflow())?,
);
}
Expand Down
22 changes: 21 additions & 1 deletion src/finding/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::{borrow::Cow, sync::LazyLock};

use anyhow::Result;
use anyhow::{anyhow, Result};
use clap::ValueEnum;
use locate::Locator;
use regex::Regex;
Expand Down Expand Up @@ -137,6 +137,13 @@ pub(crate) struct SymbolicLocation<'w> {

/// A symbolic route (of keys and indices) to the final location.
pub(crate) route: Route<'w>,

/// Whether this location is subjectively "primary" to a finding,
/// or merely a "supporting" location.
///
/// This distinction only matters in output formats like SARIF,
/// where locations are split between locations and "related" locations.
pub(crate) primary: bool,
}

impl<'w> SymbolicLocation<'w> {
Expand All @@ -146,6 +153,7 @@ impl<'w> SymbolicLocation<'w> {
annotation: self.annotation.clone(),
link: None,
route: self.route.with_keys(keys),
primary: self.primary,
}
}

Expand All @@ -169,6 +177,12 @@ impl<'w> SymbolicLocation<'w> {
self
}

/// Mark the current `SymbolicLocation` as a "primary" location.
pub(crate) fn primary(mut self) -> SymbolicLocation<'w> {
self.primary = true;
self
}

/// Concretize this `SymbolicLocation`, consuming it in the process.
pub(crate) fn concretize(self, workflow: &'w Workflow) -> Result<Location<'w>> {
let feature = Locator::new().concretize(workflow, &self)?;
Expand Down Expand Up @@ -336,6 +350,12 @@ impl<'w> FindingBuilder<'w> {
.map(|l| l.clone().concretize(workflow))
.collect::<Result<Vec<_>>>()?;

if !locations.iter().any(|l| l.symbolic.primary) {
return Err(anyhow!(
"API misuse: at least one location must be marked with primary()"
));
}

let should_ignore = self.ignored_from_inlined_comment(&locations, self.ident);

Ok(Finding {
Expand Down
1 change: 1 addition & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl Workflow {
annotation: "this workflow".to_string(),
link: None,
route: Route::new(),
primary: false,
}
}

Expand Down
15 changes: 12 additions & 3 deletions src/sarif.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,14 @@ fn build_result(registry: &WorkflowRegistry, finding: &Finding<'_>) -> SarifResu
SarifResult::builder()
.message(finding.ident)
.rule_id(finding.ident)
.locations(build_locations(registry, &finding.locations))
.locations(build_locations(
registry,
finding.locations.iter().filter(|l| l.symbolic.primary),
))
.related_locations(build_locations(
registry,
finding.locations.iter().filter(|l| !l.symbolic.primary),
))
.level(
serde_json::to_value(ResultLevel::from(finding.determinations.severity))
.expect("failed to serialize SARIF result level"),
Expand All @@ -84,9 +91,11 @@ fn build_result(registry: &WorkflowRegistry, finding: &Finding<'_>) -> SarifResu
.build()
}

fn build_locations(registry: &WorkflowRegistry, locations: &[Location<'_>]) -> Vec<SarifLocation> {
fn build_locations<'a>(
registry: &WorkflowRegistry,
locations: impl Iterator<Item = &'a Location<'a>>,
) -> Vec<SarifLocation> {
locations
.iter()
.map(|location| {
SarifLocation::builder()
.logical_locations([LogicalLocation::builder()
Expand Down

0 comments on commit f2f787f

Please sign in to comment.