Skip to content

Commit e3bdefb

Browse files
Feature: Require or recommend the timeout-minutes property for all jobs #1023
feat: add basic timeout-minutes audit for GitHub Actions jobs Implements a new audit rule that detects missing timeout-minutes property on GitHub Actions jobs to prevent runaway jobs from consuming runner minutes. What's implemented: - Basic audit structure following existing patterns (unpinned-images) - Detection of missing timeout-minutes on normal jobs - Proper finding generation with medium severity - Integration with pedantic persona - Registration in audit registry What's still needed (for future iterations): - Handle reusable workflows (jobs with 'uses' don't support timeout-minutes directly) - Check step-level timeout-minutes and transitive coverage by job timeouts - Add comprehensive test coverage - Consider different severity levels?
1 parent b2885d3 commit e3bdefb

File tree

4 files changed

+74
-2
lines changed

4 files changed

+74
-2
lines changed

crates/zizmor/src/audit/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub(crate) mod secrets_inherit;
3434
pub(crate) mod self_hosted_runner;
3535
pub(crate) mod stale_action_refs;
3636
pub(crate) mod template_injection;
37+
pub(crate) mod timeout_minutes;
3738
pub(crate) mod unpinned_images;
3839
pub(crate) mod unpinned_uses;
3940
pub(crate) mod unredacted_secrets;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use anyhow::Result;
2+
3+
use crate::{
4+
finding::{
5+
Confidence, Finding, Persona, Severity,
6+
location::{Locatable as _, SymbolicLocation},
7+
},
8+
models::workflow::JobExt as _,
9+
state::AuditState,
10+
};
11+
12+
use super::{Audit, AuditLoadError, audit_meta};
13+
14+
pub(crate) struct TimeoutMinutes;
15+
16+
impl TimeoutMinutes {
17+
fn build_finding<'doc>(
18+
&self,
19+
location: SymbolicLocation<'doc>,
20+
annotation: &str,
21+
job: &super::NormalJob<'doc>,
22+
) -> Result<Finding<'doc>> {
23+
let mut annotated_location = location;
24+
annotated_location = annotated_location.annotated(annotation);
25+
Self::finding()
26+
.severity(Severity::Medium)
27+
.confidence(Confidence::High)
28+
.add_location(annotated_location)
29+
.persona(Persona::Pedantic)
30+
.build(job.parent())
31+
}
32+
}
33+
34+
audit_meta!(
35+
TimeoutMinutes,
36+
"timeout-minutes",
37+
"missing timeout-minutes on jobs"
38+
);
39+
40+
impl Audit for TimeoutMinutes {
41+
fn new(_state: &AuditState<'_>) -> Result<Self, AuditLoadError> {
42+
Ok(Self)
43+
}
44+
45+
fn audit_normal_job<'doc>(
46+
&self,
47+
job: &super::NormalJob<'doc>,
48+
) -> anyhow::Result<Vec<Finding<'doc>>> {
49+
let mut findings = vec![];
50+
51+
// Check if timeout-minutes is missing
52+
if job.timeout_minutes.is_none() {
53+
findings.push(self.build_finding(
54+
job.location().primary(),
55+
"job is missing timeout-minutes",
56+
job,
57+
)?);
58+
}
59+
60+
Ok(findings)
61+
}
62+
}

crates/zizmor/src/registry.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
//! audits.
33
44
use std::{
5-
collections::{BTreeMap, btree_map},
5+
collections::{btree_map, BTreeMap},
66
fmt::Display,
77
process::ExitCode,
88
str::FromStr,
99
};
1010

11-
use anyhow::{Context, anyhow};
11+
use anyhow::{anyhow, Context};
1212
use camino::{Utf8Path, Utf8PathBuf};
1313
use indexmap::IndexMap;
1414
use serde::Serialize;
@@ -352,6 +352,7 @@ impl AuditRegistry {
352352
register_audit!(audit::self_hosted_runner::SelfHostedRunner);
353353
register_audit!(audit::known_vulnerable_actions::KnownVulnerableActions);
354354
register_audit!(audit::unpinned_uses::UnpinnedUses);
355+
register_audit!(audit::timeout_minutes::TimeoutMinutes);
355356
register_audit!(audit::insecure_commands::InsecureCommands);
356357
register_audit!(audit::github_env::GitHubEnv);
357358
register_audit!(audit::cache_poisoning::CachePoisoning);

test-workflow.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: Test Workflow
2+
on: push
3+
4+
jobs:
5+
test-job:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- run: echo "Hello World"

0 commit comments

Comments
 (0)