use crate::packs::{DestructivePattern, Pack, SafePattern};
use crate::{destructive_pattern, safe_pattern};
#[must_use]
pub fn create_pack() -> Pack {
Pack {
id: "featureflags.split".to_string(),
name: "Split.io",
description: "Protects against destructive Split.io CLI and API operations.",
keywords: &["split", "api.split.io"],
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!("split-splits-list", r"split\s+splits\s+list\b"),
safe_pattern!("split-splits-get", r"split\s+splits\s+get\b"),
safe_pattern!("split-splits-create", r"split\s+splits\s+create\b"),
safe_pattern!("split-splits-update", r"split\s+splits\s+update\b"),
safe_pattern!("split-environments-list", r"split\s+environments\s+list\b"),
safe_pattern!("split-environments-get", r"split\s+environments\s+get\b"),
safe_pattern!(
"split-environments-create",
r"split\s+environments\s+create\b"
),
safe_pattern!("split-segments-list", r"split\s+segments\s+list\b"),
safe_pattern!("split-segments-get", r"split\s+segments\s+get\b"),
safe_pattern!("split-segments-create", r"split\s+segments\s+create\b"),
safe_pattern!(
"split-traffic-types-list",
r"split\s+traffic-types\s+list\b"
),
safe_pattern!("split-traffic-types-get", r"split\s+traffic-types\s+get\b"),
safe_pattern!("split-workspaces-list", r"split\s+workspaces\s+list\b"),
safe_pattern!("split-workspaces-get", r"split\s+workspaces\s+get\b"),
safe_pattern!("split-help", r"split\s+(?:--help|-h|help)\b"),
safe_pattern!("split-version", r"split\s+(?:--version|version)\b"),
safe_pattern!(
"split-api-get",
r"curl\s+.*(?:-X\s+GET|--request\s+GET)\s+.*api\.split\.io"
),
]
}
fn create_destructive_patterns() -> Vec<DestructivePattern> {
vec![
destructive_pattern!(
"split-splits-delete",
r"split\s+splits\s+delete\b",
"split splits delete permanently removes a split definition. This cannot be undone.",
Critical,
"Deleting a split permanently removes the feature flag definition and all its \
targeting rules. SDKs will return control treatment for this split. Historical \
data and metrics are preserved but the split cannot be recovered.\n\n\
Safer alternatives:\n\
- split splits kill: Stop traffic without deleting\n\
- Archive the split in the UI\n\
- Export split configuration first"
),
destructive_pattern!(
"split-splits-kill",
r"split\s+splits\s+kill\b",
"split splits kill terminates a split, stopping all traffic to treatments.",
High,
"Killing a split immediately stops all treatment assignment and returns the \
default treatment to all users. Unlike delete, the split can be reactivated, \
but all users will see behavior change immediately.\n\n\
Safer alternatives:\n\
- Gradually ramp down traffic percentages first\n\
- Verify the default treatment behavior\n\
- Communicate the change to stakeholders"
),
destructive_pattern!(
"split-environments-delete",
r"split\s+environments\s+delete\b",
"split environments delete removes an environment and all its configurations.",
Critical,
"Deleting an environment removes all split configurations, targeting rules, \
and API keys for that environment. Applications using this environment will \
receive default treatments for all splits.\n\n\
Safer alternatives:\n\
- Export environment configuration\n\
- Rotate API keys before deletion\n\
- Kill all splits in the environment first"
),
destructive_pattern!(
"split-segments-delete",
r"split\s+segments\s+delete\b",
"split segments delete removes a segment and its targeting rules.",
High,
"Deleting a segment removes user grouping definitions. Splits targeting this \
segment will lose that targeting rule, changing which users receive which \
treatments.\n\n\
Safer alternatives:\n\
- Check which splits use this segment\n\
- Update split targeting before deletion\n\
- Export segment membership"
),
destructive_pattern!(
"split-traffic-types-delete",
r"split\s+traffic-types\s+delete\b",
"split traffic-types delete removes a traffic type. This affects all splits using it.",
Critical,
"Deleting a traffic type affects ALL splits configured for that traffic type. \
SDKs sending this traffic type will no longer match any splits, returning \
control treatment for all evaluations.\n\n\
Safer alternatives:\n\
- Review all splits using this traffic type\n\
- Migrate splits to a different traffic type\n\
- Ensure no SDKs are sending this traffic type"
),
destructive_pattern!(
"split-workspaces-delete",
r"split\s+workspaces\s+delete\b",
"split workspaces delete removes a workspace and all its resources.",
Critical,
"Deleting a workspace removes ALL splits, segments, environments, and API keys \
within it. This is the most destructive operation and affects all applications \
using any resource in this workspace.\n\n\
Safer alternatives:\n\
- Export complete workspace configuration\n\
- Migrate critical splits to another workspace\n\
- Contact Split.io support for assistance"
),
destructive_pattern!(
"split-api-delete-splits",
r"curl\s+.*(?:-X\s+DELETE|--request\s+DELETE)\s+.*api\.split\.io/.*/splits/",
"DELETE request to Split.io API removes split definitions.",
Critical,
"API DELETE calls to splits permanently remove feature flags without CLI \
confirmation. All targeting rules and treatments are lost immediately.\n\n\
Safer alternatives:\n\
- Use the Split CLI for confirmation prompts\n\
- GET the split configuration first\n\
- Use the Split UI for visibility into impact"
),
destructive_pattern!(
"split-api-delete-environments",
r"curl\s+.*(?:-X\s+DELETE|--request\s+DELETE)\s+.*api\.split\.io/.*/environments/",
"DELETE request to Split.io API removes environments.",
Critical,
"API DELETE calls to environments invalidate all API keys and remove all \
split configurations for that environment. Applications will lose all \
feature flag evaluations.\n\n\
Safer alternatives:\n\
- Use the Split CLI for better confirmation\n\
- Export environment configuration first\n\
- Rotate API keys before deletion"
),
destructive_pattern!(
"split-api-delete-segments",
r"curl\s+.*(?:-X\s+DELETE|--request\s+DELETE)\s+.*api\.split\.io/.*/segments/",
"DELETE request to Split.io API removes segments.",
High,
"API DELETE calls to segments remove user groupings. Splits using this \
segment will lose targeting rules, changing treatment assignment for \
affected users.\n\n\
Safer alternatives:\n\
- Check segment dependencies first\n\
- Update split targeting before deletion\n\
- Export segment membership data"
),
destructive_pattern!(
"split-api-delete-generic",
r"curl\s+.*(?:-X\s+DELETE|--request\s+DELETE)\s+.*api\.split\.io",
"DELETE request to Split.io API can remove resources.",
High,
"Generic DELETE requests to the Split.io API can remove various resources. \
Review the specific endpoint to understand what will be deleted.\n\n\
Safer alternatives:\n\
- Verify the exact resource being deleted\n\
- Use the Split CLI or UI for better visibility\n\
- GET the resource first to confirm"
),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packs::test_helpers::*;
#[test]
fn test_pack_creation() {
let pack = create_pack();
assert_eq!(pack.id, "featureflags.split");
assert_eq!(pack.name, "Split.io");
assert!(!pack.description.is_empty());
assert!(pack.keywords.contains(&"split"));
assert!(pack.keywords.contains(&"api.split.io"));
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, "split splits list");
assert_safe_pattern_matches(&pack, "split splits list --workspace my-workspace");
assert_safe_pattern_matches(&pack, "split splits get my-split");
assert_safe_pattern_matches(&pack, "split splits create --name new-split");
assert_safe_pattern_matches(&pack, "split splits update my-split --name renamed");
assert_safe_pattern_matches(&pack, "split environments list");
assert_safe_pattern_matches(&pack, "split environments get production");
assert_safe_pattern_matches(&pack, "split environments create --name staging");
assert_safe_pattern_matches(&pack, "split segments list");
assert_safe_pattern_matches(&pack, "split segments get beta-users");
assert_safe_pattern_matches(&pack, "split traffic-types list");
assert_safe_pattern_matches(&pack, "split traffic-types get user");
assert_safe_pattern_matches(&pack, "split workspaces list");
assert_safe_pattern_matches(&pack, "split workspaces get my-workspace");
assert_safe_pattern_matches(&pack, "split --help");
assert_safe_pattern_matches(&pack, "split help");
assert_safe_pattern_matches(&pack, "split --version");
assert_safe_pattern_matches(
&pack,
"curl -X GET https://api.split.io/internal/api/v2/splits",
);
}
#[test]
fn blocks_splits_delete() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"split splits delete my-split --workspace my-workspace",
"split-splits-delete",
);
}
#[test]
fn blocks_splits_kill() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"split splits kill my-split --workspace my-workspace",
"split-splits-kill",
);
}
#[test]
fn blocks_environments_delete() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"split environments delete staging --workspace my-workspace",
"split-environments-delete",
);
}
#[test]
fn blocks_segments_delete() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"split segments delete beta-users",
"split-segments-delete",
);
}
#[test]
fn blocks_traffic_types_delete() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"split traffic-types delete user",
"split-traffic-types-delete",
);
}
#[test]
fn blocks_workspaces_delete() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"split workspaces delete my-workspace",
"split-workspaces-delete",
);
}
#[test]
fn blocks_api_delete_splits() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.split.io/internal/api/v2/splits/my-split",
"split-api-delete-splits",
);
}
#[test]
fn blocks_api_delete_environments() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.split.io/internal/api/v2/environments/staging",
"split-api-delete-environments",
);
}
#[test]
fn blocks_api_delete_segments() {
let pack = create_pack();
assert_blocks_with_pattern(
&pack,
"curl -X DELETE https://api.split.io/internal/api/v2/segments/beta-users",
"split-api-delete-segments",
);
}
#[test]
fn allows_non_split_commands() {
let pack = create_pack();
assert_allows(&pack, "echo split");
assert_allows(&pack, "cat split.log");
}
}