use crate::packs::{DestructivePattern, Pack, SafePattern};
use crate::{destructive_pattern, safe_pattern};
#[must_use]
pub fn create_pack() -> Pack {
Pack {
id: "cdn.cloudflare_workers".to_string(),
name: "Cloudflare Workers",
description: "Protects against destructive Cloudflare Workers, KV, R2, and D1 operations \
via the Wrangler CLI.",
keywords: &["wrangler"],
safe_patterns: create_safe_patterns(),
destructive_patterns: create_destructive_patterns(),
keyword_matcher: None,
safe_regex_set: None,
safe_regex_set_is_complete: false,
}
}
fn create_safe_patterns() -> Vec<SafePattern> {
vec![
safe_pattern!("wrangler-whoami", r"wrangler\s+whoami\b"),
safe_pattern!("wrangler-kv-get", r"wrangler\s+kv:key\s+get\b"),
safe_pattern!("wrangler-kv-list", r"wrangler\s+kv:key\s+list\b"),
safe_pattern!(
"wrangler-kv-namespace-list",
r"wrangler\s+kv:namespace\s+list\b"
),
safe_pattern!("wrangler-r2-object-get", r"wrangler\s+r2\s+object\s+get\b"),
safe_pattern!(
"wrangler-r2-bucket-list",
r"wrangler\s+r2\s+bucket\s+list\b"
),
safe_pattern!("wrangler-d1-list", r"wrangler\s+d1\s+list\b"),
safe_pattern!("wrangler-d1-info", r"wrangler\s+d1\s+info\b"),
safe_pattern!("wrangler-dev", r"wrangler\s+dev\b"),
safe_pattern!("wrangler-tail", r"wrangler\s+tail\b"),
safe_pattern!("wrangler-version", r"wrangler\s+(?:-v|--version|version)\b"),
safe_pattern!("wrangler-help", r"wrangler\s+(?:-h|--help|help)\b"),
]
}
fn create_destructive_patterns() -> Vec<DestructivePattern> {
vec![
destructive_pattern!(
"wrangler-delete",
r"wrangler\s+delete\b",
"wrangler delete removes a Worker from Cloudflare.",
Critical,
"Deleting a Cloudflare Worker immediately stops all edge processing for that \
Worker. Any routes pointing to it will return errors. Custom domains and \
bindings (KV, R2, D1) associated with the Worker remain but become orphaned.\n\n\
Safer alternatives:\n\
- wrangler deployments list: Review deployment history first\n\
- Disable routes instead of deleting the Worker\n\
- Use wrangler tail to verify traffic before deletion"
),
destructive_pattern!(
"wrangler-deployments-rollback",
r"wrangler\s+deployments\s+rollback\b",
"wrangler deployments rollback reverts to a previous Worker version.",
High,
"Rolling back a deployment replaces your current Worker code with a previous \
version. This can reintroduce bugs, break API compatibility, or cause issues \
if the previous version relies on removed bindings or environment variables.\n\n\
Safer alternatives:\n\
- wrangler deployments list: Review available versions first\n\
- Test the target version in a staging environment\n\
- Deploy a fix forward instead of rolling back"
),
destructive_pattern!(
"wrangler-kv-key-delete",
r"wrangler\s+kv:key\s+delete\b",
"wrangler kv:key delete removes a key from KV storage.",
Medium,
"Deleting a KV key immediately removes the data at all edge locations. \
Applications reading this key will receive null or errors. KV deletions \
propagate globally within seconds.\n\n\
Safer alternatives:\n\
- wrangler kv:key get: Retrieve and backup the value first\n\
- Set an expiration instead of deleting for temporary data\n\
- Use KV namespaces for environment separation"
),
destructive_pattern!(
"wrangler-kv-namespace-delete",
r"wrangler\s+kv:namespace\s+delete\b",
"wrangler kv:namespace delete removes an entire KV namespace.",
Critical,
"Deleting a KV namespace permanently removes ALL keys and values within it. \
Any Workers bound to this namespace will fail when accessing KV. This cannot \
be undone and all data is lost.\n\n\
Safer alternatives:\n\
- wrangler kv:key list: Inventory all keys first\n\
- Export data before deletion\n\
- Remove Worker bindings before deleting the namespace"
),
destructive_pattern!(
"wrangler-kv-bulk-delete",
r"wrangler\s+kv:bulk\s+delete\b",
"wrangler kv:bulk delete removes multiple keys from KV storage.",
High,
"Bulk delete removes many KV keys at once based on a JSON file. This is \
efficient but dangerous - a malformed keys file can delete unintended data. \
All deletions are immediate and irreversible.\n\n\
Safer alternatives:\n\
- Review the keys JSON file carefully before execution\n\
- Test with a single wrangler kv:key delete first\n\
- Back up affected keys before bulk deletion"
),
destructive_pattern!(
"wrangler-r2-object-delete",
r"wrangler\s+r2\s+object\s+delete\b",
"wrangler r2 object delete removes an object from R2 storage.",
Medium,
"Deleting an R2 object permanently removes the file from storage. Any URLs \
or Workers accessing this object will receive 404 errors. Unlike S3, R2 does \
not charge for delete operations but data is unrecoverable.\n\n\
Safer alternatives:\n\
- wrangler r2 object get: Download the object first\n\
- Use object lifecycle rules for automatic expiration\n\
- Move to a separate 'archive' bucket instead of deleting"
),
destructive_pattern!(
"wrangler-r2-bucket-delete",
r"wrangler\s+r2\s+bucket\s+delete\b",
"wrangler r2 bucket delete removes an entire R2 bucket.",
Critical,
"Deleting an R2 bucket removes the bucket and ALL objects within it. Workers \
bound to this bucket will fail. The bucket name becomes available for reuse \
by any Cloudflare account.\n\n\
Safer alternatives:\n\
- wrangler r2 bucket list: Verify the bucket contents\n\
- Empty the bucket and verify it's truly unused\n\
- Remove Worker bindings before bucket deletion"
),
destructive_pattern!(
"wrangler-d1-delete",
r"wrangler\s+d1\s+delete\b",
"wrangler d1 delete removes a D1 database.",
Critical,
"Deleting a D1 database permanently removes all tables, data, and schema. \
Workers bound to this database will fail with binding errors. D1 databases \
cannot be recovered after deletion.\n\n\
Safer alternatives:\n\
- wrangler d1 export: Export the database first\n\
- wrangler d1 info: Review database details\n\
- Remove Worker bindings before deletion"
),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packs::test_helpers::*;
#[test]
fn test_pack_creation() {
let pack = create_pack();
assert_eq!(pack.id, "cdn.cloudflare_workers");
assert_eq!(pack.name, "Cloudflare Workers");
assert!(!pack.description.is_empty());
assert!(pack.keywords.contains(&"wrangler"));
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_safe_pattern_matches(&pack, "wrangler whoami");
assert_safe_pattern_matches(&pack, "wrangler kv:key get --namespace-id=abc KEY");
assert_safe_pattern_matches(&pack, "wrangler kv:key list --namespace-id=abc");
assert_safe_pattern_matches(&pack, "wrangler kv:namespace list");
assert_safe_pattern_matches(&pack, "wrangler r2 object get my-bucket/path/to/obj");
assert_safe_pattern_matches(&pack, "wrangler r2 bucket list");
assert_safe_pattern_matches(&pack, "wrangler d1 list");
assert_safe_pattern_matches(&pack, "wrangler d1 info my-db");
assert_safe_pattern_matches(&pack, "wrangler dev");
assert_safe_pattern_matches(&pack, "wrangler tail");
assert_safe_pattern_matches(&pack, "wrangler --version");
assert_safe_pattern_matches(&pack, "wrangler -v");
assert_safe_pattern_matches(&pack, "wrangler help");
}
#[test]
fn blocks_destructive_commands() {
let pack = create_pack();
assert_blocks_with_pattern(&pack, "wrangler delete", "wrangler-delete");
assert_blocks_with_pattern(&pack, "wrangler delete my-worker", "wrangler-delete");
assert_blocks_with_pattern(
&pack,
"wrangler deployments rollback",
"wrangler-deployments-rollback",
);
assert_blocks_with_pattern(
&pack,
"wrangler kv:key delete --namespace-id=abc KEY",
"wrangler-kv-key-delete",
);
assert_blocks_with_pattern(
&pack,
"wrangler kv:namespace delete --namespace-id=abc",
"wrangler-kv-namespace-delete",
);
assert_blocks_with_pattern(
&pack,
"wrangler kv:bulk delete --namespace-id=abc keys.json",
"wrangler-kv-bulk-delete",
);
assert_blocks_with_pattern(
&pack,
"wrangler r2 object delete bucket/key",
"wrangler-r2-object-delete",
);
assert_blocks_with_pattern(
&pack,
"wrangler r2 bucket delete my-bucket",
"wrangler-r2-bucket-delete",
);
assert_blocks_with_pattern(&pack, "wrangler d1 delete my-db", "wrangler-d1-delete");
}
}