Skip to content

Commit 3e9f0ad

Browse files
committed
feat: Audit secrets outside an environment
1 parent 67fdebf commit 3e9f0ad

File tree

5 files changed

+107
-10
lines changed

5 files changed

+107
-10
lines changed

src/audit/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub(crate) mod known_vulnerable_actions;
2626
pub(crate) mod overprovisioned_secrets;
2727
pub(crate) mod ref_confusion;
2828
pub(crate) mod secrets_inherit;
29+
pub(crate) mod secrets_outside_environment;
2930
pub(crate) mod self_hosted_runner;
3031
pub(crate) mod template_injection;
3132
pub(crate) mod unpinned_uses;
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use github_actions_models::{
2+
common::{expr::LoE, Env, EnvValue},
3+
workflow::job::StepBody,
4+
};
5+
6+
use super::{audit_meta, Audit};
7+
use crate::{finding::Confidence, models::Job};
8+
9+
pub(crate) struct SecretsOutsideEnvironment;
10+
11+
audit_meta!(
12+
SecretsOutsideEnvironment,
13+
"secrets-outside-environment",
14+
"secrets used without an environment to gate them"
15+
);
16+
17+
impl Audit for SecretsOutsideEnvironment {
18+
fn new(_state: super::AuditState) -> anyhow::Result<Self>
19+
where
20+
Self: Sized,
21+
{
22+
Ok(Self)
23+
}
24+
25+
fn audit_raw<'w>(
26+
&self,
27+
input: &'w super::AuditInput,
28+
) -> anyhow::Result<Vec<crate::finding::Finding<'w>>> {
29+
let mut findings = vec![];
30+
31+
if let super::AuditInput::Workflow(w) = input {
32+
for job in w.jobs() {
33+
if let Job::NormalJob(j) = job {
34+
if j.environment().is_some() {
35+
continue;
36+
}
37+
38+
for step in j.steps() {
39+
let body = &step.body;
40+
let eenv: &Env;
41+
42+
match body {
43+
StepBody::Uses { uses: _, with } => {
44+
eenv = with;
45+
}
46+
StepBody::Run {
47+
run: _,
48+
shell: _,
49+
env,
50+
working_directory: _,
51+
} => match env {
52+
LoE::Expr(_) => {
53+
panic!("We don't handle Expr yet!")
54+
}
55+
LoE::Literal(env) => eenv = env,
56+
},
57+
}
58+
59+
for v in eenv.values() {
60+
if let EnvValue::String(s) = v {
61+
if s.contains("secrets") {
62+
findings.push(
63+
Self::finding()
64+
.add_location(step.location().primary())
65+
.confidence(Confidence::High)
66+
.severity(crate::finding::Severity::High)
67+
.build(input)?,
68+
);
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
Ok(findings)
78+
}
79+
}

src/main.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use std::{
2-
io::{Write, stdout},
2+
io::{stdout, Write},
33
process::ExitCode,
44
str::FromStr,
55
};
66

77
use annotate_snippets::{Level, Renderer};
88
use anstream::{eprintln, stream::IsTerminal};
9-
use anyhow::{Context, Result, anyhow};
9+
use anyhow::{anyhow, Context, Result};
1010
use audit::Audit;
1111
use camino::{Utf8Path, Utf8PathBuf};
1212
use clap::{Parser, ValueEnum};
@@ -21,9 +21,9 @@ use models::Action;
2121
use owo_colors::OwoColorize;
2222
use registry::{AuditRegistry, FindingRegistry, InputRegistry};
2323
use state::AuditState;
24-
use tracing::{Span, info_span, instrument};
25-
use tracing_indicatif::{IndicatifLayer, span_ext::IndicatifSpanExt};
26-
use tracing_subscriber::{EnvFilter, layer::SubscriberExt as _, util::SubscriberInitExt as _};
24+
use tracing::{info_span, instrument, Span};
25+
use tracing_indicatif::{span_ext::IndicatifSpanExt, IndicatifLayer};
26+
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter};
2727

2828
mod audit;
2929
mod config;
@@ -501,6 +501,7 @@ fn run() -> Result<ExitCode> {
501501
register_audit!(audit::github_env::GitHubEnv);
502502
register_audit!(audit::cache_poisoning::CachePoisoning);
503503
register_audit!(audit::secrets_inherit::SecretsInherit);
504+
register_audit!(audit::secrets_outside_environment::SecretsOutsideEnvironment);
504505
register_audit!(audit::bot_conditions::BotConditions);
505506
register_audit!(audit::overprovisioned_secrets::OverprovisionedSecrets);
506507
register_audit!(audit::unredacted_secrets::UnredactedSecrets);

src/models.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ use std::collections::HashMap;
55
use std::fmt::Debug;
66
use std::{iter::Enumerate, ops::Deref};
77

8-
use anyhow::{Context, Result, bail};
8+
use anyhow::{bail, Context, Result};
99
use camino::Utf8Path;
10-
use github_actions_models::common::Env;
1110
use github_actions_models::common::expr::LoE;
11+
use github_actions_models::common::Env;
1212
use github_actions_models::workflow::event::{BareEvent, OptionalBody};
13-
use github_actions_models::workflow::job::{RunsOn, Strategy};
14-
use github_actions_models::workflow::{self, Trigger, job, job::StepBody};
13+
use github_actions_models::workflow::job::{DeploymentEnvironment, RunsOn, Strategy};
14+
use github_actions_models::workflow::{self, job, job::StepBody, Trigger};
1515
use github_actions_models::{action, common};
1616
use indexmap::IndexMap;
1717
use line_index::LineIndex;
18-
use serde_json::{Value, json};
18+
use serde_json::{json, Value};
1919
use terminal_link::Link;
2020

2121
use crate::finding::{Route, SymbolicLocation};
@@ -210,6 +210,10 @@ impl<'w> NormalJob<'w> {
210210
Steps::new(self)
211211
}
212212

213+
pub(crate) fn environment(&self) -> Option<&DeploymentEnvironment> {
214+
self.inner.environment.as_ref()
215+
}
216+
213217
/// Perform feats of heroism to figure of what this job's runner's
214218
/// default shell is.
215219
///
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: Action
2+
on: push
3+
jobs:
4+
build:
5+
name: Job
6+
runs-on: ubuntu-latest
7+
steps:
8+
- name: Docker setup
9+
uses: stacks-network/actions/docker@main
10+
with:
11+
username: ${{ secrets.DOCKERHUB_USERNAME }}
12+
password: ${{ secrets.DOCKERHUB_PASSWORD }}

0 commit comments

Comments
 (0)