Skip to content

Commit 99b6212

Browse files
authored
Merge pull request #5644 from Turbo87/false-false-positives
github/secret_scanning: Consider revoked tokens as true positive
2 parents 3808ef8 + 6b11081 commit 99b6212

File tree

3 files changed

+47
-32
lines changed

3 files changed

+47
-32
lines changed

src/controllers/github/secret_scanning.rs

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::models::{ApiToken, User};
33
use crate::schema::api_tokens;
44
use crate::util::read_fill;
55
use crate::util::token::SecureToken;
6+
use anyhow::{anyhow, Context};
67
use base64;
78
use once_cell::sync::Lazy;
89
use ring::signature;
@@ -154,50 +155,64 @@ fn alert_revoke_token(
154155

155156
let hashed_token = SecureToken::hash(&alert.token);
156157

157-
// not using ApiToken::find_by_api_token in order to preserve last_used_at
158-
// the token field has a uniqueness constraint so get_result() should be safe to use
159-
let token = diesel::update(api_tokens::table)
158+
// Not using `ApiToken::find_by_api_token()` in order to preserve `last_used_at`
159+
let token = api_tokens::table
160160
.filter(api_tokens::token.eq(hashed_token))
161-
.filter(api_tokens::revoked.eq(false))
162-
.set(api_tokens::revoked.eq(true))
163161
.get_result::<ApiToken>(&*conn)
164162
.optional()?;
165163

166164
let Some(token) = token else {
165+
debug!("Unknown API token received (false positive)");
167166
return Ok(GitHubSecretAlertFeedbackLabel::FalsePositive);
168167
};
169168

170-
// send email notification to the token owner
171-
let user = User::find(&conn, token.user_id)?;
169+
if token.revoked {
170+
debug!(
171+
token_id = %token.id, user_id = %token.user_id,
172+
"Already revoked API token received (true positive)",
173+
);
174+
return Ok(GitHubSecretAlertFeedbackLabel::TruePositive);
175+
}
176+
177+
diesel::update(&token)
178+
.set(api_tokens::revoked.eq(true))
179+
.execute(&*conn)?;
180+
172181
warn!(
173-
gh_login = %user.gh_login, user_id = %user.id, token_id = %token.id,
174-
"Revoked API token",
182+
token_id = %token.id, user_id = %token.user_id,
183+
"Active API token received and revoked (true positive)",
175184
);
176185

177-
if let Some(email) = user.email(&conn)? {
178-
let result = req.app().emails.send_token_exposed_notification(
179-
&email,
180-
&alert.url,
181-
"GitHub",
182-
&alert.source,
183-
&token.name,
184-
);
185-
if let Err(error) = result {
186-
warn!(
187-
gh_login = %user.gh_login, user_id = %user.id, ?error,
188-
"Failed to send email notification",
189-
);
190-
}
191-
} else {
186+
if let Err(error) = send_notification_email(&token, alert, req) {
192187
warn!(
193-
gh_login = %user.gh_login, user_id = %user.id, error = "No address found",
188+
token_id = %token.id, user_id = %token.user_id, ?error,
194189
"Failed to send email notification",
195-
);
196-
};
190+
)
191+
}
197192

198193
Ok(GitHubSecretAlertFeedbackLabel::TruePositive)
199194
}
200195

196+
fn send_notification_email(
197+
token: &ApiToken,
198+
alert: &GitHubSecretAlert,
199+
req: &dyn RequestExt,
200+
) -> anyhow::Result<()> {
201+
let conn = req.db_read()?;
202+
203+
let user = User::find(&conn, token.user_id).context("Failed to find user")?;
204+
let Some(email) = user.email(&conn)? else {
205+
return Err(anyhow!("No address found"));
206+
};
207+
208+
req.app()
209+
.emails
210+
.send_token_exposed_notification(&email, &alert.url, "GitHub", &alert.source, &token.name)
211+
.map_err(|error| anyhow!("{error}"))?;
212+
213+
Ok(())
214+
}
215+
201216
#[derive(Deserialize, Serialize)]
202217
pub struct GitHubSecretAlertFeedback {
203218
pub token_raw: String,

src/models/user.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ impl User {
171171
}
172172

173173
/// Queries for the email belonging to a particular user
174-
pub fn email(&self, conn: &PgConnection) -> AppResult<Option<String>> {
175-
Ok(Email::belonging_to(self)
174+
pub fn email(&self, conn: &PgConnection) -> QueryResult<Option<String>> {
175+
Email::belonging_to(self)
176176
.select(emails::email)
177177
.first(conn)
178-
.optional()?)
178+
.optional()
179179
}
180180
}

src/tests/github_secret_scanning.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ fn github_secret_alert_for_revoked_token() {
109109
let response = anon.run::<Vec<GitHubSecretAlertFeedback>>(request);
110110
assert_eq!(response.status(), StatusCode::OK);
111111

112-
// Ensure feedback is a false positive
112+
// Ensure feedback is a true positive
113113
let feedback = response.good();
114114
assert_eq!(feedback.len(), 1);
115115
assert_eq!(feedback[0].token_raw, "some_token");
116116
assert_eq!(feedback[0].token_type, "some_type");
117117
assert_eq!(
118118
feedback[0].label,
119-
GitHubSecretAlertFeedbackLabel::FalsePositive
119+
GitHubSecretAlertFeedbackLabel::TruePositive
120120
);
121121

122122
// Ensure that the token is still revoked

0 commit comments

Comments
 (0)