Skip to content

Commit b87e2d3

Browse files
acidghostwoodruffw
andauthored
bugfix: sanitize gh_token & avoid panic (#1027)
Co-authored-by: William Woodruff <[email protected]>
1 parent acf0b32 commit b87e2d3

14 files changed

+96
-71
lines changed

crates/zizmor/src/audit/artipacked.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,7 @@ mod tests {
208208
let audit_state = AuditState {
209209
config: &Default::default(),
210210
no_online_audits: false,
211-
cache_dir: "/tmp/zizmor".into(),
212-
gh_token: None,
211+
gh_client: None,
213212
gh_hostname: GitHubHost::Standard("github.com".into()),
214213
};
215214
let audit = <$audit_type>::new(&audit_state).unwrap();

crates/zizmor/src/audit/bot_conditions.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,7 @@ mod tests {
415415
let audit_state = AuditState {
416416
config: &Default::default(),
417417
no_online_audits: false,
418-
cache_dir: "/tmp/zizmor".into(),
419-
gh_token: None,
418+
gh_client: None,
420419
gh_hostname: GitHubHost::Standard("github.com".into()),
421420
};
422421
let audit = <$audit_type>::new(&audit_state).unwrap();

crates/zizmor/src/audit/cache_poisoning.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,7 @@ mod tests {
467467
let audit_state = AuditState {
468468
config: &Default::default(),
469469
no_online_audits: false,
470-
cache_dir: "/tmp/zizmor".into(),
471-
gh_token: None,
470+
gh_client: None,
472471
gh_hostname: GitHubHost::Standard("github.com".into()),
473472
};
474473
let audit = <$audit_type>::new(&audit_state).unwrap();

crates/zizmor/src/audit/github_env.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,7 @@ mod tests {
523523
let audit_state = AuditState {
524524
config: &Default::default(),
525525
no_online_audits: false,
526-
cache_dir: "/tmp/zizmor".into(),
527-
gh_token: None,
526+
gh_client: None,
528527
gh_hostname: GitHubHost::Standard("github.com".into()),
529528
};
530529

@@ -640,8 +639,7 @@ mod tests {
640639
let audit_state = AuditState {
641640
config: &Default::default(),
642641
no_online_audits: false,
643-
cache_dir: "/tmp/zizmor".into(),
644-
gh_token: None,
642+
gh_client: None,
645643
gh_hostname: GitHubHost::Standard("github.com".into()),
646644
};
647645

crates/zizmor/src/audit/impostor_commit.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,11 @@ impl Audit for ImpostorCommit {
120120
)));
121121
}
122122

123-
let Some(client) = state.github_client() else {
124-
return Err(AuditLoadError::Skip(anyhow!(
125-
"can't run without a GitHub API token"
126-
)));
127-
};
128-
129-
Ok(ImpostorCommit { client })
123+
state
124+
.gh_client
125+
.clone()
126+
.ok_or_else(|| AuditLoadError::Skip(anyhow!("can't run without a GitHub API token")))
127+
.map(|client| ImpostorCommit { client })
130128
}
131129

132130
fn audit_workflow<'doc>(&self, workflow: &'doc Workflow) -> Result<Vec<Finding<'doc>>> {

crates/zizmor/src/audit/known_vulnerable_actions.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,11 @@ impl Audit for KnownVulnerableActions {
154154
)));
155155
}
156156

157-
let Some(client) = state.github_client() else {
158-
return Err(AuditLoadError::Skip(anyhow!(
159-
"can't run without a GitHub API token"
160-
)));
161-
};
162-
163-
Ok(Self { client })
157+
state
158+
.gh_client
159+
.clone()
160+
.ok_or_else(|| AuditLoadError::Skip(anyhow!("can't run without a GitHub API token")))
161+
.map(|client| KnownVulnerableActions { client })
164162
}
165163

166164
fn audit_step<'doc>(&self, step: &Step<'doc>) -> Result<Vec<Finding<'doc>>> {

crates/zizmor/src/audit/ref_confusion.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,11 @@ impl Audit for RefConfusion {
5959
)));
6060
}
6161

62-
let Some(client) = state.github_client() else {
63-
return Err(AuditLoadError::Skip(anyhow!(
64-
"can't run without a GitHub API token"
65-
)));
66-
};
67-
68-
Ok(Self { client })
62+
state
63+
.gh_client
64+
.clone()
65+
.ok_or_else(|| AuditLoadError::Skip(anyhow!("can't run without a GitHub API token")))
66+
.map(|client| RefConfusion { client })
6967
}
7068

7169
fn audit_workflow<'doc>(

crates/zizmor/src/audit/stale_action_refs.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,11 @@ impl Audit for StaleActionRefs {
6767
)));
6868
}
6969

70-
let Some(client) = state.github_client() else {
71-
return Err(AuditLoadError::Skip(anyhow!(
72-
"can't run without a GitHub API token"
73-
)));
74-
};
75-
76-
Ok(Self { client })
70+
state
71+
.gh_client
72+
.clone()
73+
.ok_or_else(|| AuditLoadError::Skip(anyhow!("can't run without a GitHub API token")))
74+
.map(|client| StaleActionRefs { client })
7775
}
7876

7977
fn audit_step<'w>(&self, step: &Step<'w>) -> Result<Vec<Finding<'w>>> {

crates/zizmor/src/audit/template_injection.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,8 +605,7 @@ mod tests {
605605
let audit_state = AuditState {
606606
config: &Default::default(),
607607
no_online_audits: false,
608-
cache_dir: "/tmp/zizmor".into(),
609-
gh_token: None,
608+
gh_client: None,
610609
gh_hostname: GitHubHost::Standard("github.com".into()),
611610
};
612611
let audit = <$audit_type>::new(&audit_state).unwrap();

crates/zizmor/src/github_api.rs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use http_cache_reqwest::{
1515
use owo_colors::OwoColorize;
1616
use reqwest::{
1717
Response, StatusCode,
18-
header::{ACCEPT, AUTHORIZATION, HeaderMap, USER_AGENT},
18+
header::{ACCEPT, AUTHORIZATION, HeaderMap, HeaderValue, InvalidHeaderValue, USER_AGENT},
1919
};
2020
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
2121
use serde::{Deserialize, de::DeserializeOwned};
@@ -61,20 +61,43 @@ impl GitHubHost {
6161
}
6262
}
6363

64+
/// A sanitized GitHub access token.
65+
#[derive(Clone)]
66+
pub(crate) struct GitHubToken(String);
67+
68+
impl GitHubToken {
69+
pub(crate) fn from_clap(token: &str) -> Result<Self, String> {
70+
let token = token.trim();
71+
if token.is_empty() {
72+
return Err("GitHub token cannot be empty".into());
73+
}
74+
Ok(Self(token.to_owned()))
75+
}
76+
77+
fn to_header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
78+
HeaderValue::from_str(&format!("Bearer {}", self.0))
79+
}
80+
}
81+
82+
#[derive(Clone)]
6483
pub(crate) struct Client {
6584
api_base: String,
6685
http: ClientWithMiddleware,
6786
}
6887

6988
impl Client {
70-
pub(crate) fn new(hostname: &GitHubHost, token: &str, cache_dir: &Path) -> Self {
89+
pub(crate) fn new(
90+
hostname: &GitHubHost,
91+
token: &GitHubToken,
92+
cache_dir: &Path,
93+
) -> anyhow::Result<Self> {
7194
let mut headers = HeaderMap::new();
7295
headers.insert(USER_AGENT, "zizmor".parse().unwrap());
7396
headers.insert(
7497
AUTHORIZATION,
75-
format!("Bearer {token}")
76-
.parse()
77-
.expect("couldn't build authorization header for GitHub client?"),
98+
token
99+
.to_header_value()
100+
.context("couldn't build authorization header for GitHub client")?,
78101
);
79102
headers.insert("X-GitHub-Api-Version", "2022-11-28".parse().unwrap());
80103
headers.insert(ACCEPT, "application/vnd.github+json".parse().unwrap());
@@ -105,10 +128,10 @@ impl Client {
105128
}))
106129
.build();
107130

108-
Self {
131+
Ok(Self {
109132
api_base: hostname.to_api_url(),
110133
http,
111-
}
134+
})
112135
}
113136

114137
async fn paginate<T: DeserializeOwned>(
@@ -525,7 +548,7 @@ pub(crate) struct File {
525548

526549
#[cfg(test)]
527550
mod tests {
528-
use crate::github_api::GitHubHost;
551+
use crate::github_api::{GitHubHost, GitHubToken};
529552

530553
#[test]
531554
fn test_github_host() {
@@ -540,4 +563,23 @@ mod tests {
540563
assert_eq!(GitHubHost::from_clap(host).unwrap().to_api_url(), expected);
541564
}
542565
}
566+
567+
#[test]
568+
fn test_github_token() {
569+
for (token, expected) in [
570+
("gha_testtest\n", "gha_testtest"),
571+
(" gha_testtest ", "gha_testtest"),
572+
("gho_testtest", "gho_testtest"),
573+
("gho_test\ntest", "gho_test\ntest"),
574+
] {
575+
assert_eq!(GitHubToken::from_clap(token).unwrap().0, expected);
576+
}
577+
}
578+
579+
#[test]
580+
fn test_github_token_err() {
581+
for token in ["", " ", "\r", "\n", "\t", " "] {
582+
assert!(GitHubToken::from_clap(token).is_err());
583+
}
584+
}
543585
}

0 commit comments

Comments
 (0)