use crate::packs::{DestructivePattern, Pack, SafePattern};
use crate::{destructive_pattern, safe_pattern};
#[must_use]
pub fn create_pack() -> Pack {
Pack {
id: "storage.minio".to_string(),
name: "MinIO",
description: "Protects against destructive MinIO Client (mc) operations like bucket \
removal, object deletion, and admin operations.",
keywords: &["mc"],
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!("mc-ls", r"\bmc\s+(?:--\S+\s+)*ls\b"),
safe_pattern!("mc-cat", r"\bmc\s+(?:--\S+\s+)*cat\b"),
safe_pattern!("mc-head", r"\bmc\s+(?:--\S+\s+)*head\b"),
safe_pattern!("mc-stat", r"\bmc\s+(?:--\S+\s+)*stat\b"),
safe_pattern!("mc-cp", r"\bmc\s+(?:--\S+\s+)*cp\b"),
safe_pattern!("mc-diff", r"\bmc\s+(?:--\S+\s+)*diff\b"),
safe_pattern!("mc-find", r"\bmc\s+(?:--\S+\s+)*find\b"),
safe_pattern!("mc-du", r"\bmc\s+(?:--\S+\s+)*du\b"),
safe_pattern!("mc-version", r"\bmc\s+(?:--\S+\s+)*version\b"),
safe_pattern!("mc-help", r"\bmc\s+(?:--\S+\s+)*(?:--help|-h)\b"),
safe_pattern!("mc-admin-info", r"\bmc\s+(?:--\S+\s+)*admin\s+info\b"),
safe_pattern!(
"mc-admin-user-list",
r"\bmc\s+(?:--\S+\s+)*admin\s+user\s+list\b"
),
safe_pattern!(
"mc-admin-policy-list",
r"\bmc\s+(?:--\S+\s+)*admin\s+policy\s+list\b"
),
safe_pattern!("mc-alias-list", r"\bmc\s+(?:--\S+\s+)*alias\s+list\b"),
]
}
fn create_destructive_patterns() -> Vec<DestructivePattern> {
vec![
destructive_pattern!(
"mc-rb",
r"\bmc\s+(?:--\S+\s+)*rb\b",
"mc rb removes a MinIO bucket.",
Critical,
"Removing a MinIO bucket deletes the bucket and all objects within it. With \
--force, objects are deleted without confirmation. Applications referencing \
this bucket will fail immediately.\n\n\
Safer alternatives:\n\
- mc ls: List bucket contents first\n\
- mc cp --recursive: Backup to another location\n\
- Enable versioning or object locking before testing"
),
destructive_pattern!(
"mc-rm",
r"\bmc\s+(?:--\S+\s+)*rm\b",
"mc rm deletes objects from MinIO.",
High,
"Deleting MinIO objects permanently removes data unless versioning is enabled. \
With --recursive, entire directory trees are deleted. With --force, no \
confirmation is requested.\n\n\
Safer alternatives:\n\
- mc ls: Preview files to be deleted\n\
- mc cp: Backup objects before deletion\n\
- Enable bucket versioning for recovery"
),
destructive_pattern!(
"mc-admin-bucket-delete",
r"\bmc\s+(?:--\S+\s+)*admin\s+bucket\s+(?:delete|remove)\b",
"mc admin bucket delete removes a bucket via admin API.",
Critical,
"The admin bucket delete command bypasses normal bucket deletion restrictions. \
This can remove buckets that are protected or non-empty. The operation is \
immediate and cannot be undone.\n\n\
Safer alternatives:\n\
- mc admin info: Review cluster configuration\n\
- mc ls: Verify bucket contents first\n\
- Use standard mc rb for safer deletion"
),
destructive_pattern!(
"mc-mirror-remove",
r"\bmc\s+(?:--\S+\s+)*mirror\b.*--remove\b",
"mc mirror --remove deletes destination objects not in source.",
High,
"The --remove flag deletes objects from the destination that don't exist in \
the source. If source and destination are swapped, or source is empty, this \
can result in complete data loss at the destination.\n\n\
Safer alternatives:\n\
- mc diff: Preview differences before mirroring\n\
- mc mirror without --remove: Only adds/updates\n\
- Backup destination before mirroring"
),
destructive_pattern!(
"mc-admin-user-remove",
r"\bmc\s+(?:--\S+\s+)*admin\s+user\s+(?:remove|disable)\b",
"mc admin user remove/disable affects user access.",
High,
"Removing or disabling a MinIO user revokes their access to all buckets and \
policies. Applications using this user's credentials will fail to authenticate. \
Service accounts created by this user may also be affected.\n\n\
Safer alternatives:\n\
- mc admin user info: Review user permissions first\n\
- mc admin user disable: Disable instead of remove\n\
- Rotate credentials before removing user"
),
destructive_pattern!(
"mc-admin-policy-remove",
r"\bmc\s+(?:--\S+\s+)*admin\s+policy\s+(?:remove|unset)\b",
"mc admin policy remove/unset modifies access policies.",
Medium,
"Removing or unsetting a policy affects all users and groups assigned to it. \
Users may unexpectedly lose access to buckets they need. Policy changes take \
effect immediately.\n\n\
Safer alternatives:\n\
- mc admin policy info: Review policy details\n\
- mc admin user list: Check who uses this policy\n\
- Create replacement policy before removing old one"
),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packs::test_helpers::*;
#[test]
fn test_pack_creation() {
let pack = create_pack();
assert_eq!(pack.id, "storage.minio");
assert_eq!(pack.name, "MinIO");
assert!(!pack.description.is_empty());
assert!(pack.keywords.contains(&"mc"));
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, "mc ls myminio/bucket");
assert_safe_pattern_matches(&pack, "mc --json ls myminio/bucket");
assert_safe_pattern_matches(&pack, "mc cat myminio/bucket/file.txt");
assert_safe_pattern_matches(&pack, "mc head myminio/bucket/file.txt");
assert_safe_pattern_matches(&pack, "mc stat myminio/bucket/file.txt");
assert_safe_pattern_matches(&pack, "mc cp localfile myminio/bucket/");
assert_safe_pattern_matches(&pack, "mc cp myminio/bucket/file.txt ./local");
assert_safe_pattern_matches(&pack, "mc diff myminio/bucket1 myminio/bucket2");
assert_safe_pattern_matches(&pack, "mc find myminio/bucket --name '*.txt'");
assert_safe_pattern_matches(&pack, "mc du myminio/bucket");
assert_safe_pattern_matches(&pack, "mc version");
assert_safe_pattern_matches(&pack, "mc --help");
assert_safe_pattern_matches(&pack, "mc admin info myminio");
assert_safe_pattern_matches(&pack, "mc admin user list myminio");
assert_safe_pattern_matches(&pack, "mc admin policy list myminio");
assert_safe_pattern_matches(&pack, "mc alias list");
}
#[test]
fn blocks_destructive_commands() {
let pack = create_pack();
assert_blocks_with_pattern(&pack, "mc rb myminio/bucket", "mc-rb");
assert_blocks_with_pattern(&pack, "mc rb --force myminio/bucket", "mc-rb");
assert_blocks_with_pattern(&pack, "mc --json rb myminio/bucket", "mc-rb");
assert_blocks_with_pattern(&pack, "mc rm myminio/bucket/file.txt", "mc-rm");
assert_blocks_with_pattern(&pack, "mc rm --recursive myminio/bucket/", "mc-rm");
assert_blocks_with_pattern(&pack, "mc rm --force myminio/bucket/file.txt", "mc-rm");
assert_blocks_with_pattern(
&pack,
"mc admin bucket delete myminio bucket",
"mc-admin-bucket-delete",
);
assert_blocks_with_pattern(
&pack,
"mc admin bucket remove myminio bucket",
"mc-admin-bucket-delete",
);
assert_blocks_with_pattern(
&pack,
"mc mirror --remove myminio/src myminio/dst",
"mc-mirror-remove",
);
assert_blocks_with_pattern(
&pack,
"mc mirror myminio/src myminio/dst --remove",
"mc-mirror-remove",
);
assert_blocks_with_pattern(
&pack,
"mc admin user remove myminio username",
"mc-admin-user-remove",
);
assert_blocks_with_pattern(
&pack,
"mc admin user disable myminio username",
"mc-admin-user-remove",
);
assert_blocks_with_pattern(
&pack,
"mc admin policy remove myminio policyname",
"mc-admin-policy-remove",
);
assert_blocks_with_pattern(
&pack,
"mc admin policy unset myminio policyname",
"mc-admin-policy-remove",
);
}
}