Skip to content

Commit 6715f67

Browse files
committed
Add email template for Trusted Publishing token exposure notifications
This commit implements a `TrustedPublishingTokenExposedEmail` template that will be used to notify users when their trusted publishing tokens are revoked due to GitHub secret scanning alerts. The template handles both single and multiple crate scenarios and provides security guidance.
1 parent bef3a5c commit 6715f67

File tree

1 file changed

+59
-1
lines changed

1 file changed

+59
-1
lines changed

src/controllers/github/secret_scanning.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ async fn alert_revoke_token(
145145
.await?;
146146

147147
if deleted_count > 0 {
148-
warn!("Active Trusted Publishing token received and revoked (true positive)");
148+
warn!("Active Trusted Publishing token received and revoked (true positive)");
149149
return Ok(GitHubSecretAlertFeedbackLabel::TruePositive);
150150
} else {
151151
debug!("Unknown Trusted Publishing token received (false positive)");
@@ -264,6 +264,64 @@ Source type: {source}",
264264
}
265265
}
266266

267+
struct TrustedPublishingTokenExposedEmail<'a> {
268+
domain: &'a str,
269+
reporter: &'a str,
270+
source: &'a str,
271+
crate_names: &'a [String],
272+
url: &'a str,
273+
}
274+
275+
impl Email for TrustedPublishingTokenExposedEmail<'_> {
276+
fn subject(&self) -> String {
277+
"crates.io: Your Trusted Publishing token has been revoked".to_string()
278+
}
279+
280+
fn body(&self) -> String {
281+
let authorization = if self.crate_names.len() == 1 {
282+
format!(
283+
"This token was only authorized to publish the \"{}\" crate.",
284+
self.crate_names[0]
285+
)
286+
} else {
287+
format!(
288+
"This token was authorized to publish the following crates: \"{}\".",
289+
self.crate_names.join("\", \"")
290+
)
291+
};
292+
293+
let mut body = format!(
294+
"{reporter} has notified us that one of your crates.io Trusted Publishing tokens \
295+
has been exposed publicly. We have revoked this token as a precaution.
296+
297+
{authorization}
298+
299+
Please review your account at https://{domain} and your GitHub repository \
300+
settings to confirm that no unexpected changes have been made to your crates \
301+
or trusted publishing configurations.
302+
303+
Source type: {source}",
304+
domain = self.domain,
305+
reporter = self.reporter,
306+
source = self.source,
307+
);
308+
309+
if self.url.is_empty() {
310+
body.push_str("\n\nWe were not informed of the URL where the token was found.");
311+
} else {
312+
body.push_str(&format!("\n\nURL where the token was found: {}", self.url));
313+
}
314+
315+
body.push_str(
316+
"\n\nTrusted Publishing tokens are temporary and used for automated \
317+
publishing from GitHub Actions. If this exposure was unexpected, please review \
318+
your repository's workflow files and secrets.",
319+
);
320+
321+
body
322+
}
323+
}
324+
267325
#[derive(Deserialize, Serialize)]
268326
pub struct GitHubSecretAlertFeedback {
269327
pub token_raw: String,

0 commit comments

Comments
 (0)