use destructive_command_guard::packs::PackRegistry;
use destructive_command_guard::packs::regex_engine::needs_backtracking_engine;
use std::collections::{HashMap, HashSet};
#[test]
#[allow(clippy::too_many_lines)]
fn test_audit_backtracking_requirements() {
let expected_backtracking: HashMap<&str, HashSet<&str>> = HashMap::from([
(
"apigateway.apigee",
HashSet::from([
"apigeecli-apis-get",
"apigeecli-apis-list",
"apigeecli-developers-get",
"apigeecli-developers-list",
"apigeecli-envs-get",
"apigeecli-envs-list",
"apigeecli-orgs-get",
"apigeecli-orgs-list",
"apigeecli-products-get",
"apigeecli-products-list",
"gcloud-apigee-apis-describe",
"gcloud-apigee-apis-list",
"gcloud-apigee-deployments-describe",
"gcloud-apigee-deployments-list",
"gcloud-apigee-developers-describe",
"gcloud-apigee-developers-list",
"gcloud-apigee-environments-describe",
"gcloud-apigee-environments-list",
"gcloud-apigee-organizations-describe",
"gcloud-apigee-organizations-list",
"gcloud-apigee-products-describe",
"gcloud-apigee-products-list",
]),
),
(
"apigateway.aws",
HashSet::from([
"apigateway-get-api-key",
"apigateway-get-api-keys",
"apigateway-get-authorizers",
"apigateway-get-deployment",
"apigateway-get-deployments",
"apigateway-get-domain-names",
"apigateway-get-method",
"apigateway-get-models",
"apigateway-get-resource",
"apigateway-get-resources",
"apigateway-get-rest-api",
"apigateway-get-rest-apis",
"apigateway-get-stage",
"apigateway-get-stages",
"apigateway-get-usage-plans",
"apigateway-help",
"apigatewayv2-get-api",
"apigatewayv2-get-apis",
"apigatewayv2-get-authorizers",
"apigatewayv2-get-domain-names",
"apigatewayv2-get-integration",
"apigatewayv2-get-integrations",
"apigatewayv2-get-route",
"apigatewayv2-get-routes",
"apigatewayv2-get-stage",
"apigatewayv2-get-stages",
"apigatewayv2-help",
]),
),
(
"apigateway.kong",
HashSet::from([
"deck-convert",
"deck-diff",
"deck-dump",
"deck-file",
"deck-help",
"deck-ping",
"deck-validate",
"deck-version",
"kong-check",
"kong-config-parse",
"kong-health",
"kong-help",
"kong-version",
]),
),
(
"backup.borg",
HashSet::from([
"borg-check",
"borg-create",
"borg-diff",
"borg-extract",
"borg-info",
"borg-list",
"borg-mount",
]),
),
(
"backup.rclone",
HashSet::from([
"rclone-check",
"rclone-config",
"rclone-copy",
"rclone-ls",
"rclone-lsd",
"rclone-lsl",
"rclone-size",
]),
),
(
"backup.restic",
HashSet::from([
"restic-backup",
"restic-check",
"restic-diff",
"restic-find",
"restic-ls",
"restic-restore",
"restic-snapshots",
"restic-stats",
]),
),
(
"backup.velero",
HashSet::from([
"velero-backup-create",
"velero-backup-describe",
"velero-backup-get",
"velero-backup-logs",
"velero-restore-create",
"velero-schedule-get",
"velero-version",
]),
),
(
"cdn.cloudflare_workers",
HashSet::from([
"wrangler-d1-info",
"wrangler-d1-list",
"wrangler-dev",
"wrangler-help",
"wrangler-kv-get",
"wrangler-kv-list",
"wrangler-kv-namespace-list",
"wrangler-r2-bucket-list",
"wrangler-r2-object-get",
"wrangler-tail",
"wrangler-version",
"wrangler-whoami",
]),
),
(
"cdn.fastly",
HashSet::from([
"fastly-backend-describe",
"fastly-backend-list",
"fastly-domain-describe",
"fastly-domain-list",
"fastly-help",
"fastly-profile",
"fastly-service-describe",
"fastly-service-list",
"fastly-service-search",
"fastly-vcl-describe",
"fastly-vcl-list",
"fastly-version",
"fastly-version-list",
"fastly-whoami",
]),
),
(
"cicd.github_actions",
HashSet::from([
"gh-actions-api-delete-secrets",
"gh-actions-api-delete-variables",
"gh-actions-api-explicit-get",
"gh-actions-run-cancel",
"gh-actions-run-list",
"gh-actions-run-view",
"gh-actions-secret-list",
"gh-actions-secret-remove",
"gh-actions-variable-list",
"gh-actions-variable-remove",
"gh-actions-workflow-disable",
"gh-actions-workflow-list",
"gh-actions-workflow-view",
]),
),
(
"cloud.aws",
HashSet::from(["athena-delete-with-where", "s3-cp", "s3-ls", "sts-identity"]),
),
(
"cloud.azure",
HashSet::from([
"az-account",
"az-configure",
"az-list",
"az-login",
"az-show",
"az-version",
]),
),
(
"cloud.gcp",
HashSet::from([
"gcloud-auth",
"gcloud-config",
"gcloud-describe",
"gcloud-info",
"gcloud-list",
"gsutil-cp",
"gsutil-ls",
"gsutil-rb",
]),
),
(
"containers.compose",
HashSet::from(["compose-down-no-volumes"]),
),
(
"containers.docker",
HashSet::from([
"docker-build",
"docker-exec",
"docker-images",
"docker-inspect",
"docker-logs",
"docker-ps",
"docker-pull",
"docker-run",
"docker-stats",
]),
),
(
"containers.podman",
HashSet::from([
"podman-build",
"podman-exec",
"podman-images",
"podman-inspect",
"podman-logs",
"podman-ps",
"podman-pull",
"podman-run",
]),
),
(
"core.filesystem",
HashSet::from([
"rm-f-r-tmp",
"rm-f-r-tmpdir",
"rm-f-r-tmpdir-brace",
"rm-f-r-var-tmp",
"rm-force-recursive-tmp",
"rm-force-recursive-tmpdir",
"rm-force-recursive-tmpdir-brace",
"rm-force-recursive-var-tmp",
"rm-fr-tmp",
"rm-fr-tmpdir",
"rm-fr-tmpdir-brace",
"rm-fr-tmpdir-brace-quoted",
"rm-fr-tmpdir-quoted",
"rm-fr-var-tmp",
"rm-r-f-tmp",
"rm-r-f-tmpdir",
"rm-r-f-tmpdir-brace",
"rm-r-f-var-tmp",
"rm-recursive-force-tmp",
"rm-recursive-force-tmpdir",
"rm-recursive-force-tmpdir-brace",
"rm-recursive-force-var-tmp",
"rm-rf-tmp",
"rm-rf-tmpdir",
"rm-rf-tmpdir-brace",
"rm-rf-tmpdir-brace-quoted",
"rm-rf-tmpdir-quoted",
"rm-rf-var-tmp",
]),
),
(
"core.git",
HashSet::from([
"checkout-ref-discard",
"push-force-long",
"restore-staged-long",
"restore-staged-short",
"restore-worktree",
]),
),
(
"database.mongodb",
HashSet::from([
"mongo-aggregate",
"mongo-count",
"mongo-explain",
"mongo-find",
"mongodump-no-drop",
]),
),
("database.mysql", HashSet::from(["mysqldump-no-drop"])),
("database.postgresql", HashSet::from(["pg-dump-no-clean"])),
(
"database.redis",
HashSet::from([
"redis-config-get",
"redis-dbsize",
"redis-get",
"redis-info",
"redis-keys",
"redis-scan",
"shutdown",
]),
),
("dns.generic", HashSet::from(["dns-dig-safe"])),
(
"email.ses",
HashSet::from([
"ses-get-send-quota",
"ses-get-template",
"ses-list-identities",
"ses-list-templates",
"sesv2-get-account",
]),
),
(
"featureflags.flipt",
HashSet::from([
"flipt-config",
"flipt-evaluate",
"flipt-flag-create",
"flipt-flag-get",
"flipt-flag-list",
"flipt-flag-update",
"flipt-help",
"flipt-namespace-create",
"flipt-namespace-get",
"flipt-namespace-list",
"flipt-rule-create",
"flipt-rule-get",
"flipt-rule-list",
"flipt-segment-create",
"flipt-segment-get",
"flipt-segment-list",
"flipt-server",
"flipt-version",
]),
),
(
"featureflags.launchdarkly",
HashSet::from([
"ldcli-environments-create",
"ldcli-environments-get",
"ldcli-environments-list",
"ldcli-flags-create",
"ldcli-flags-get",
"ldcli-flags-list",
"ldcli-flags-update",
"ldcli-help",
"ldcli-metrics-get",
"ldcli-metrics-list",
"ldcli-projects-create",
"ldcli-projects-get",
"ldcli-projects-list",
"ldcli-segments-create",
"ldcli-segments-get",
"ldcli-segments-list",
"ldcli-version",
]),
),
(
"featureflags.split",
HashSet::from([
"split-environments-create",
"split-environments-get",
"split-environments-list",
"split-help",
"split-segments-create",
"split-segments-get",
"split-segments-list",
"split-splits-create",
"split-splits-get",
"split-splits-list",
"split-splits-update",
"split-traffic-types-get",
"split-traffic-types-list",
"split-version",
"split-workspaces-get",
"split-workspaces-list",
]),
),
(
"featureflags.unleash",
HashSet::from([
"unleash-environments-get",
"unleash-environments-list",
"unleash-features-create",
"unleash-features-enable",
"unleash-features-get",
"unleash-features-list",
"unleash-features-update",
"unleash-help",
"unleash-projects-create",
"unleash-projects-get",
"unleash-projects-list",
"unleash-strategies-get",
"unleash-strategies-list",
"unleash-version",
]),
),
(
"infrastructure.ansible",
HashSet::from(["playbook-all-hosts"]),
),
(
"infrastructure.pulumi",
HashSet::from([
"destroy",
"pulumi-about",
"pulumi-config",
"pulumi-logs",
"pulumi-preview",
"pulumi-stack-init",
"pulumi-stack-ls",
"pulumi-stack-select",
"pulumi-version",
"pulumi-whoami",
]),
),
(
"infrastructure.terraform",
HashSet::from([
"destroy",
"terraform-fmt",
"terraform-graph",
"terraform-init",
"terraform-output",
"terraform-plan",
"terraform-providers",
"terraform-show",
"terraform-state-list",
"terraform-state-show",
"terraform-validate",
"terraform-version",
]),
),
(
"kubernetes.helm",
HashSet::from([
"helm-diff",
"helm-get",
"helm-history",
"helm-inspect",
"helm-lint",
"helm-list",
"helm-repo",
"helm-search",
"helm-show",
"helm-status",
"helm-template",
"rollback",
"uninstall",
]),
),
(
"kubernetes.kubectl",
HashSet::from([
"delete-pv",
"delete-pvc",
"delete-workload",
"kubectl-api",
"kubectl-config",
"kubectl-describe",
"kubectl-diff",
"kubectl-explain",
"kubectl-get",
"kubectl-logs",
"kubectl-top",
"kubectl-version",
]),
),
(
"kubernetes.kustomize",
HashSet::from(["kubectl-delete-k", "kubectl-kustomize", "kustomize-build"]),
),
(
"loadbalancer.traefik",
HashSet::from(["traefik-healthcheck", "traefik-version"]),
),
(
"messaging.nats",
HashSet::from([
"nats-bench",
"nats-consumer-info",
"nats-consumer-ls",
"nats-kv-get",
"nats-kv-ls",
"nats-pub",
"nats-server-info",
"nats-stream-info",
"nats-stream-ls",
"nats-sub",
]),
),
(
"monitoring.datadog",
HashSet::from(["datadog-ci-dashboards-list", "datadog-ci-monitors-list"]),
),
(
"monitoring.newrelic",
HashSet::from([
"newrelic-apm-app-get",
"newrelic-entity-search",
"newrelic-query",
]),
),
(
"monitoring.splunk",
HashSet::from(["splunk-list", "splunk-search", "splunk-show"]),
),
(
"package_managers",
HashSet::from([
"apt-get-list",
"apt-list",
"apt-remove",
"brew-uninstall",
"cargo-publish",
"cargo-yank",
"npm-audit",
"npm-install",
"npm-list",
"npm-publish",
"npm-unpublish",
"pip-list",
"pip-uninstall",
"pnpm-install",
"pnpm-publish",
"poetry-env-list",
"poetry-publish",
"poetry-remove",
"poetry-show",
"yarn-add",
"yarn-audit",
"yarn-list",
"yarn-publish",
"yum-remove",
]),
),
(
"platform.github",
HashSet::from([
"gh-api-delete-actions-secret",
"gh-api-delete-actions-variable",
"gh-api-delete-deploy-key",
"gh-api-delete-hook",
"gh-api-delete-release",
"gh-api-delete-repo",
"gh-api-explicit-get",
"gh-auth-status",
"gh-gist-delete",
"gh-gist-list-view",
"gh-issue-delete",
"gh-issue-list-view",
"gh-release-delete",
"gh-release-list-view",
"gh-repo-archive",
"gh-repo-delete",
"gh-repo-deploy-key-delete",
"gh-repo-list-view",
"gh-run-cancel",
"gh-secret-delete",
"gh-secret-list",
"gh-ssh-key-delete",
"gh-ssh-key-list",
"gh-status",
"gh-variable-delete",
"gh-variable-list",
]),
),
(
"remote.scp",
HashSet::from(["scp-to-home", "scp-to-tmp", "scp-to-var-tmp"]),
),
(
"secrets.aws_secrets",
HashSet::from([
"aws-secretsmanager-describe",
"aws-secretsmanager-get",
"aws-secretsmanager-get-random-password",
"aws-secretsmanager-get-resource-policy",
"aws-secretsmanager-list",
"aws-secretsmanager-list-versions",
"aws-ssm-describe-parameters",
"aws-ssm-get-parameter",
"aws-ssm-get-parameters",
]),
),
(
"secrets.doppler",
HashSet::from([
"doppler-configs-list",
"doppler-configure",
"doppler-environments-list",
"doppler-projects-list",
"doppler-run",
"doppler-secrets-get",
"doppler-secrets-list",
"doppler-setup",
]),
),
(
"secrets.onepassword",
HashSet::from([
"op-account-get",
"op-document-get",
"op-group-list",
"op-item-get",
"op-item-list",
"op-read",
"op-user-list",
"op-vault-get",
"op-vault-list",
"op-whoami",
]),
),
(
"secrets.vault",
HashSet::from([
"vault-audit-list",
"vault-auth-list",
"vault-kv-get",
"vault-kv-list",
"vault-lease-lookup",
"vault-policy-list",
"vault-read",
"vault-secrets-list",
"vault-status",
"vault-token-lookup",
"vault-version",
]),
),
(
"storage.azure_blob",
HashSet::from([
"az-storage-account-keys-list",
"az-storage-account-list",
"az-storage-account-show",
"az-storage-blob-download",
"az-storage-blob-download-batch",
"az-storage-blob-exists",
"az-storage-blob-list",
"az-storage-blob-metadata-show",
"az-storage-blob-show",
"az-storage-blob-url",
"az-storage-container-exists",
"az-storage-container-list",
"az-storage-container-show",
"azcopy-copy",
"azcopy-env",
"azcopy-jobs-list",
"azcopy-jobs-show",
"azcopy-list",
"azcopy-login",
]),
),
(
"storage.gcs",
HashSet::from([
"gcloud-storage-buckets-describe",
"gcloud-storage-buckets-list",
"gcloud-storage-cat",
"gcloud-storage-cp",
"gcloud-storage-ls",
"gcloud-storage-objects-describe",
"gcloud-storage-objects-list",
"gsutil-cat",
"gsutil-cp",
"gsutil-du",
"gsutil-hash",
"gsutil-help",
"gsutil-ls",
"gsutil-rb",
"gsutil-rm",
"gsutil-stat",
"gsutil-version",
]),
),
(
"storage.minio",
HashSet::from([
"mc-admin-info",
"mc-admin-policy-list",
"mc-admin-user-list",
"mc-alias-list",
"mc-cat",
"mc-cp",
"mc-diff",
"mc-du",
"mc-find",
"mc-head",
"mc-ls",
"mc-stat",
"mc-version",
]),
),
(
"storage.s3",
HashSet::from([
"s3-copy",
"s3-list",
"s3-mb",
"s3-presign",
"s3api-delete-object",
"s3api-get-object",
"s3api-head-object",
"s3api-list-objects",
]),
),
(
"system.disk",
HashSet::from([
"btrfs-device-stats",
"btrfs-filesystem-df",
"btrfs-filesystem-show",
"btrfs-filesystem-usage",
"btrfs-property-get",
"btrfs-scrub-status",
"btrfs-subvolume-list",
"btrfs-subvolume-show",
"dmsetup-deps",
"dmsetup-info",
"dmsetup-ls",
"dmsetup-status",
"dmsetup-table",
"fdisk-edit",
"parted-modify",
]),
),
("system.permissions", HashSet::from(["chmod-non-recursive"])),
(
"system.services",
HashSet::from([
"service-status",
"systemctl-cat",
"systemctl-is",
"systemctl-list",
"systemctl-reload",
"systemctl-show",
"systemctl-status",
]),
),
]);
let registry = PackRegistry::new();
let all_ids: HashSet<String> = registry
.all_pack_ids()
.into_iter()
.map(String::from)
.collect();
let pack_infos = registry.list_packs(&all_ids);
let mut unexpected = Vec::new();
let empty_set = HashSet::new();
for info in pack_infos {
let pack = registry.get(&info.id).unwrap();
let pack_expected = expected_backtracking
.get(pack.id.as_str())
.unwrap_or(&empty_set);
for p in &pack.safe_patterns {
let pattern_str = p.regex.as_str();
if needs_backtracking_engine(pattern_str) {
if !pack_expected.contains(p.name) {
unexpected.push(format!(
"Unexpected backtracking in SafePattern: {}/{} ({})",
pack.id, p.name, pattern_str
));
}
} else if pack_expected.contains(p.name) {
unexpected.push(format!(
"Expected backtracking in SafePattern but not found: {}/{} ({})",
pack.id, p.name, pattern_str
));
}
}
for p in &pack.destructive_patterns {
let pattern_str = p.regex.as_str();
let name = p.name.unwrap_or("UNNAMED");
if needs_backtracking_engine(pattern_str) {
if !pack_expected.contains(name) {
unexpected.push(format!(
"Unexpected backtracking in DestructivePattern: {}/{} ({})",
pack.id, name, pattern_str
));
}
} else if pack_expected.contains(name) {
unexpected.push(format!(
"Expected backtracking in DestructivePattern but not found: {}/{} ({})",
pack.id, name, pattern_str
));
}
}
}
assert!(
unexpected.is_empty(),
"Audit mismatch:\n{}",
unexpected.join("\n")
);
}