use crate::destructive_pattern;
use crate::packs::{DestructivePattern, Pack, SafePattern};
#[must_use]
pub fn create_pack() -> Pack {
Pack {
id: "email.sendgrid".to_string(),
name: "SendGrid",
description: "Protects against destructive SendGrid API operations like template deletion, \
API key deletion, and domain authentication removal.",
keywords: &["sendgrid", "api.sendgrid.com"],
safe_patterns: create_safe_patterns(),
destructive_patterns: create_destructive_patterns(),
keyword_matcher: None,
safe_regex_set: None,
safe_regex_set_is_complete: false,
}
}
const fn create_safe_patterns() -> Vec<SafePattern> {
vec![]
}
fn create_destructive_patterns() -> Vec<DestructivePattern> {
vec![
destructive_pattern!(
"sendgrid-delete-template",
r"(?:-X\s*DELETE|--request\s+DELETE).*sendgrid\.com/v3/templates/|sendgrid\.com/v3/templates/\S+.*(?:-X\s*DELETE|--request\s+DELETE)",
"DELETE to SendGrid /v3/templates removes a transactional template.",
High,
"Deleting a template breaks any email sends referencing that template ID. \
Applications will receive errors when trying to send with a deleted template. \
All versions of the template are removed.\n\n\
Safer alternatives:\n\
- GET /v3/templates/{id}: Export template before deletion\n\
- Create a new version instead of deleting\n\
- Check email code for template ID references"
),
destructive_pattern!(
"sendgrid-delete-api-key",
r"(?:-X\s*DELETE|--request\s+DELETE).*sendgrid\.com/v3/api_keys/|sendgrid\.com/v3/api_keys/\S+.*(?:-X\s*DELETE|--request\s+DELETE)",
"DELETE to SendGrid /v3/api_keys removes an API key.",
Critical,
"Deleting an API key immediately revokes access. All applications using this \
key will fail to authenticate. You cannot recover the key value after deletion.\n\n\
Safer alternatives:\n\
- Create new API key before deleting old one\n\
- Update applications with new key first\n\
- Use API key scopes to limit damage"
),
destructive_pattern!(
"sendgrid-delete-whitelabel-domain",
r"(?:-X\s*DELETE|--request\s+DELETE).*sendgrid\.com/v3/whitelabel/domains/|sendgrid\.com/v3/whitelabel/domains/\d+.*(?:-X\s*DELETE|--request\s+DELETE)",
"DELETE to SendGrid /v3/whitelabel/domains removes domain authentication.",
Critical,
"Deleting domain authentication removes DKIM and SPF records. Emails sent \
from this domain will fail authentication checks, leading to delivery issues \
and potential spam classification.\n\n\
Safer alternatives:\n\
- Document DNS records before deletion\n\
- Verify new domain authentication is working\n\
- Plan for DNS propagation delays"
),
destructive_pattern!(
"sendgrid-delete-sender",
r"(?:-X\s*DELETE|--request\s+DELETE).*sendgrid\.com/v3/(?:senders|verified_senders)/|sendgrid\.com/v3/(?:senders|verified_senders)/\d+.*(?:-X\s*DELETE|--request\s+DELETE)",
"DELETE to SendGrid /v3/senders or /v3/verified_senders removes a sender identity.",
High,
"Deleting a sender identity prevents sending from that email address. \
Marketing campaigns and transactional emails using this sender will fail. \
Re-verification may require email confirmation.\n\n\
Safer alternatives:\n\
- Create new sender identity before deleting\n\
- Update email configurations first\n\
- Check for scheduled campaigns using this sender"
),
destructive_pattern!(
"sendgrid-delete-teammate",
r"(?:-X\s*DELETE|--request\s+DELETE).*sendgrid\.com/v3/teammates/|sendgrid\.com/v3/teammates/\w+.*(?:-X\s*DELETE|--request\s+DELETE)",
"DELETE to SendGrid /v3/teammates removes a teammate from the account.",
Medium,
"Deleting a teammate revokes their access to the SendGrid account. They lose \
access to templates, statistics, and settings they may have been managing.\n\n\
Safer alternatives:\n\
- Change teammate permissions instead of deleting\n\
- Ensure knowledge transfer is complete\n\
- Verify teammate doesn't own critical configurations"
),
destructive_pattern!(
"sendgrid-delete-suppression",
r"(?:-X\s*DELETE|--request\s+DELETE).*sendgrid\.com/v3/(?:suppression|asm)/",
"DELETE to SendGrid suppression endpoints removes entries from suppression lists.",
High,
"Removing suppression entries allows emails to be sent to addresses that \
previously bounced, complained, or unsubscribed. This can damage sender \
reputation and violate CAN-SPAM compliance.\n\n\
Safer alternatives:\n\
- Verify the email address is valid before removing\n\
- Check bounce reason codes\n\
- Respect unsubscribe requests (may be legally required)"
),
destructive_pattern!(
"sendgrid-delete-webhook",
r"(?:-X\s*DELETE|--request\s+DELETE).*sendgrid\.com/v3/user/webhooks/",
"DELETE to SendGrid /v3/user/webhooks removes a webhook configuration.",
Medium,
"Deleting a webhook stops event notifications to your application. Bounce \
handling, open tracking, and click tracking data will not be received. \
This can affect email analytics and automation.\n\n\
Safer alternatives:\n\
- Document webhook URL and event types\n\
- Test new webhook before removing old one\n\
- Verify no critical automations depend on events"
),
destructive_pattern!(
"sendgrid-delete-subuser",
r"(?:-X\s*DELETE|--request\s+DELETE).*sendgrid\.com/v3/subusers/|sendgrid\.com/v3/subusers/\w+.*(?:-X\s*DELETE|--request\s+DELETE)",
"DELETE to SendGrid /v3/subusers removes a subuser account.",
Critical,
"Deleting a subuser removes all their data including templates, statistics, \
and API keys. Applications using subuser credentials will fail immediately. \
This cannot be undone.\n\n\
Safer alternatives:\n\
- Export subuser data and configurations\n\
- Migrate to new subuser before deleting\n\
- Disable subuser access instead of deleting"
),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packs::test_helpers::*;
#[test]
fn test_pack_creation() {
let pack = create_pack();
assert_eq!(pack.id, "email.sendgrid");
assert_eq!(pack.name, "SendGrid");
assert!(!pack.description.is_empty());
assert!(pack.keywords.contains(&"sendgrid"));
assert!(pack.keywords.contains(&"api.sendgrid.com"));
assert_patterns_compile(&pack);
assert_all_patterns_have_reasons(&pack);
assert_unique_pattern_names(&pack);
}
#[test]
fn allows_safe_commands() {
let pack = create_pack();
assert_allows(&pack, "curl https://api.sendgrid.com/v3/templates");
assert_allows(&pack, "curl https://api.sendgrid.com/v3/templates/d-abc123");
assert_allows(&pack, "curl https://api.sendgrid.com/v3/api_keys");
assert_allows(&pack, "curl https://api.sendgrid.com/v3/stats");
assert_allows(
&pack,
"curl -X POST https://api.sendgrid.com/v3/mail/send -d '{}'",
);
assert_allows(&pack, "curl https://api.sendgrid.com/v3/whitelabel/domains");
assert_allows(&pack, "curl https://api.sendgrid.com/v3/senders");
assert_allows(&pack, "curl https://api.sendgrid.com/v3/user/profile");
}
#[test]
fn blocks_destructive_commands() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/templates/d-abc123",
"sendgrid-delete-template",
);
assert_blocks_with_pattern(
&pack,
"curl --request DELETE https://api.sendgrid.com/v3/templates/d-abc123",
"sendgrid-delete-template",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/api_keys/abc123",
"sendgrid-delete-api-key",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/whitelabel/domains/12345",
"sendgrid-delete-whitelabel-domain",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/senders/12345",
"sendgrid-delete-sender",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/verified_senders/12345",
"sendgrid-delete-sender",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/teammates/username",
"sendgrid-delete-teammate",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/suppression/bounces/email@test.com",
"sendgrid-delete-suppression",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/asm/groups/123",
"sendgrid-delete-suppression",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/user/webhooks/event/settings",
"sendgrid-delete-webhook",
);
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.sendgrid.com/v3/subusers/username",
"sendgrid-delete-subuser",
);
}
}