diff --git a/shellfirm/checks/aws.yaml b/shellfirm/checks/aws.yaml index 0659ffd..d2d405e 100644 --- a/shellfirm/checks/aws.yaml +++ b/shellfirm/checks/aws.yaml @@ -14,7 +14,7 @@ - type: NotContains value: "--dryrun" - from: aws - test: aws\s+ec2\s+terminate-instances + test: aws\s+(?:--\S+\s+\S+\s+)*ec2\s+terminate-instances description: "Terminating EC2 instances permanently destroys them and their local storage." id: aws:ec2_terminate severity: High @@ -28,7 +28,7 @@ alternative: "aws rds delete-db-instance --skip-final-snapshot=false --final-db-snapshot-identifier " alternative_info: "Create a final snapshot before deletion so data can be recovered." - from: aws - test: aws\s+iam\s+delete-(user|role|policy|group) + test: aws\s+iam\s+delete-(user|role|policy|group)(\s|$) description: "Deleting IAM resources can break access for services and users." id: aws:iam_delete severity: High diff --git a/shellfirm/checks/base.yaml b/shellfirm/checks/base.yaml index 571c723..28d54ed 100644 --- a/shellfirm/checks/base.yaml +++ b/shellfirm/checks/base.yaml @@ -14,12 +14,12 @@ id: base:execute_all_history_commands severity: Critical - from: base - test: reboot(\s|$) + test: (^|\s)reboot(\s|$) description: "You are going to reboot your machine." id: base:reboot_machine severity: High - from: base - test: shutdown(\s|$) + test: (^|\s)shutdown(\s|$) description: "You are going to shutdown your machine." id: base:shutdown_machine severity: High @@ -46,10 +46,20 @@ id: systemd:disable_service severity: Medium - from: base - test: systemctl\s+stop\s+(docker|sshd|nginx|apache2|httpd|postgresql|mysql|redis)(\s|$) + test: systemctl\s+stop\s+(docker|sshd|nginx|apache2|httpd|postgresql|mysql|redis)[\w.-]*(\s|$) description: "Stopping a critical system service can cause outages." id: systemd:stop_critical_service severity: High +- from: base + test: (halt|poweroff)(\s|$) + description: "This command will immediately power off your machine." + id: base:poweroff_machine + severity: High +- from: base + test: init\s+(0|6)(\s|$) + description: "This command will shutdown (init 0) or reboot (init 6) your machine." + id: base:init_shutdown_reboot + severity: High - from: base test: ssh-add\s+-D description: "Removes all SSH identities from the agent." diff --git a/shellfirm/checks/database.yaml b/shellfirm/checks/database.yaml index 5a78ee2..1f3381c 100644 --- a/shellfirm/checks/database.yaml +++ b/shellfirm/checks/database.yaml @@ -41,7 +41,12 @@ id: database:drop_schema_role_user severity: High - from: database - test: (?i)ALTER\s+TABLE\s+\w+\s+DROP\s+COLUMN + test: (?i)ALTER\s+TABLE\s+[\w.]+\s+DROP\s+COLUMN description: "Dropping a column permanently removes data from all rows." id: database:alter_drop_column severity: High +- from: database + test: (?i)DROP\s+INDEX + description: "Dropping an index on a production table can cause catastrophic performance degradation." + id: database:drop_index + severity: Medium diff --git a/shellfirm/checks/docker.yaml b/shellfirm/checks/docker.yaml index 811d5c4..8ffbb5f 100644 --- a/shellfirm/checks/docker.yaml +++ b/shellfirm/checks/docker.yaml @@ -6,7 +6,7 @@ alternative: "docker system prune" alternative_info: "Without -a, only dangling images are removed (not all unused images)." - from: docker - test: docker\s+rm\s+(-f|--force)\s+\$\(docker\s+ps + test: docker\s+rm\s+(-f|--force)\s+(?:\$\(|`)docker\s+ps description: "Force removing all running or stopped containers." id: docker:force_remove_all_containers severity: High @@ -31,7 +31,7 @@ id: docker:remove_network severity: Medium - from: docker - test: docker[\s-]compose\s+down\s+.*(-v|--volumes) + test: docker[\s-]compose\s+.*down\s+.*(-v|--volumes) description: "This will stop containers AND delete all associated volumes and data." id: docker:compose_down_volumes severity: High @@ -59,3 +59,11 @@ description: "Clears the entire Docker build cache." id: docker:buildx_prune_all severity: High +- from: docker + test: docker\s+run\s+(.+\s+)?--privileged(\s|$|=true) + description: "Running a container with --privileged gives it full access to the host system." + id: docker:run_privileged + severity: High + filters: + - type: NotContains + value: "--privileged=false" diff --git a/shellfirm/checks/flyio.yaml b/shellfirm/checks/flyio.yaml new file mode 100644 index 0000000..011e10b --- /dev/null +++ b/shellfirm/checks/flyio.yaml @@ -0,0 +1,20 @@ +- from: flyio + test: fly(?:ctl)?\s+apps\s+destroy(\s|$) + description: "Destroying a Fly.io app permanently removes it and all its resources." + id: flyio:apps_destroy + severity: Critical +- from: flyio + test: fly(?:ctl)?\s+secrets\s+unset\s+ + description: "Removing secrets can break running applications that depend on them." + id: flyio:secrets_unset + severity: High +- from: flyio + test: fly(?:ctl)?\s+volumes?\s+destroy(\s|$) + description: "Destroying a volume permanently deletes all data stored on it." + id: flyio:volumes_destroy + severity: High +- from: flyio + test: fly(?:ctl)?\s+postgres\s+destroy(\s|$) + description: "Destroying a Fly Postgres cluster permanently deletes the database and all its data." + id: flyio:postgres_destroy + severity: Critical diff --git a/shellfirm/checks/fs.yaml b/shellfirm/checks/fs.yaml index 0050936..9591ef8 100644 --- a/shellfirm/checks/fs.yaml +++ b/shellfirm/checks/fs.yaml @@ -1,5 +1,5 @@ - from: fs - test: 'rm\s{1,}(?:-[rRfvV]+|--(?:force|recursive|verbose|preserve-root|no-preserve-root|one-file-system))(?:\s+(?:-[rRfvV]+|--(?:force|recursive|verbose|preserve-root|no-preserve-root|one-file-system)))*(?:\s+\S+)*?\s+(\*|\.{1,}|/)(?:\s|$)' + test: 'rm\s{1,}(?:-[rRfvV]+|--(?:force|recursive|verbose|preserve-root|no-preserve-root|one-file-system))(?:\s+(?:-[rRfvV]+|--(?:force|recursive|verbose|preserve-root|no-preserve-root|one-file-system)))*(?:\s+\S+)*?\s+(\*|\.{1,}|/|\.\/\*)(?:\s|$)' description: "You are going to delete everything in the path." id: fs:recursively_delete severity: Critical @@ -125,7 +125,7 @@ - type: PathExists value: 1 - from: fs - test: rsync\s+.*--delete + test: rsync\s+.*--delete(\s|$) description: "Syncs with deletion — removes files at destination not present in source." id: fs:rsync_delete severity: High @@ -141,3 +141,15 @@ description: "Recursive ownership change on root or wildcard can break system files and SSH keys." id: fs:recursively_chown severity: Critical +- from: fs + test: shred\s+ + description: "Shred overwrites file data at the block level, making recovery impossible unlike rm." + id: fs:shred + severity: High + alternative: "rm " + alternative_info: "Regular rm only unlinks the file. Use shred only when you truly need irrecoverable deletion." +- from: fs + test: truncate\s+.*-s\s*0\s + description: "This command will zero out a file, permanently erasing its contents." + id: fs:truncate_zero + severity: High diff --git a/shellfirm/checks/git.yaml b/shellfirm/checks/git.yaml index 6c17086..bf4a91e 100644 --- a/shellfirm/checks/git.yaml +++ b/shellfirm/checks/git.yaml @@ -9,12 +9,12 @@ alternative: "git stash" alternative_info: "Saves your changes to the stash so you can recover them later with 'git stash pop'." - from: git - test: git\s{1,}rm\s{1,}(\*|\.) + test: git\s{1,}rm\s{1,}(?:-\S+\s+)*(\*|\.)(\s|$) description: "This command going to delete all files." id: git:delete_all severity: High - from: git - test: git\s{1,}clean\s{1,}(?:-[a-zA-Z]*f[a-zA-Z]*d[a-zA-Z]*|-[a-zA-Z]*d[a-zA-Z]*f[a-zA-Z]*|(?:-\S+\s+)*-f(?:\s+-\S+)*\s+-d|(?:-\S+\s+)*-d(?:\s+-\S+)*\s+-f) + test: git\s{1,}clean\s{1,}(?:-[a-zA-Z]*f[a-zA-Z]*d[a-zA-Z]*|-[a-zA-Z]*d[a-zA-Z]*f[a-zA-Z]*|(?:-\S+\s+)*-f(?:\s+-\S+)*\s+-d|(?:-\S+\s+)*-d(?:\s+-\S+)*\s+-f|--force\s+(?:-\S+\s+)*-d|(?:-\S+\s+)*-d(?:\s+-\S+)*\s+--force) description: "This command will remove all untracked files and directories." id: git:clean_force severity: High @@ -33,6 +33,8 @@ filters: - type: NotContains value: "--force-with-lease" + - type: NotContains + value: "--force-if-includes" alternative: "git push --force-with-lease" alternative_info: "Checks that your local ref is up-to-date before force pushing, preventing accidental overwrites of others' work." - from: git @@ -43,7 +45,7 @@ alternative: "git branch -d " alternative_info: "Uses safe delete (-d) which refuses to delete a branch with unmerged changes." - from: git - test: git\s{1,}checkout\s{1,}(-f|--force) + test: git\s{1,}checkout\s{1,}.*(-f\b|--force) description: "This command will force checkout and discard local changes." id: git:force_checkout severity: High @@ -64,7 +66,7 @@ alternative: "git stash" alternative_info: "Saves your changes to the stash so you can recover them later with 'git stash pop'." - from: git - test: git\s{1,}rebase\s{1,}-i + test: git\s{1,}rebase\s{1,}(-i\b|--interactive) description: "This command will start an interactive rebase which can modify commit history." id: git:interactive_rebase severity: Medium @@ -76,12 +78,12 @@ alternative: "git-filter-repo" alternative_info: "A faster, safer, and officially recommended alternative to filter-branch." - from: git - test: git\s{1,}gc\s{1,}--prune=now + test: git\s{1,}gc\s{1,}.*--prune=now description: "This command will permanently delete unreachable objects." id: git:gc_prune severity: High - from: git - test: git\s{1,}update-ref\s{1,}-d + test: git\s{1,}update-ref\s{1,}(-d\b|--delete) description: "This command will delete a Git reference." id: git:delete_ref severity: High @@ -125,13 +127,20 @@ description: "This command will apply or remove stashed changes." id: git-strict:stash_pop_drop severity: Low +- from: git + test: git\s{1,}stash\s{1,}clear(\s|$) + description: "This command will permanently delete all stash entries with no way to recover them." + id: git:stash_clear + severity: High + alternative: "git stash list" + alternative_info: "Review your stashes before clearing them. Use 'git stash drop stash@{N}' to remove individual entries." - from: git test: git\s{1,}submodule\s{1,}(update|deinit) description: "This command will update or deinitialize Git submodules." id: git-strict:submodule_update severity: Low - from: git - test: git\s{1,}tag\s{1,}-a + test: git\s{1,}tag\s{1,}(-a\b|--annotate) description: "This command will create an annotated tag." id: git-strict:create_tag severity: Low @@ -141,12 +150,12 @@ id: git:push_mirror severity: Critical - from: git - test: git\s{1,}push\s{1,}\S+\s{1,}--delete\s|git\s{1,}push\s{1,}\S+\s{1,}:\S + test: git\s{1,}push\s{1,}(\S+\s{1,}--delete\s|--delete\s{1,}\S+\s|\S+\s{1,}:\S) description: "This command will delete a remote branch." id: git:push_delete_branch severity: High - from: git - test: git\s{1,}reflog\s{1,}expire\s{1,}--expire=now + test: git\s{1,}reflog\s{1,}expire\s{1,}.*--expire=now description: "Expiring all reflog entries destroys the last recovery mechanism for lost commits." id: git:reflog_expire severity: Critical diff --git a/shellfirm/checks/github.yaml b/shellfirm/checks/github.yaml new file mode 100644 index 0000000..c8bbf04 --- /dev/null +++ b/shellfirm/checks/github.yaml @@ -0,0 +1,35 @@ +- from: github + test: gh\s+repo\s+delete(\s|$) + description: "Permanently deletes a repository and all its data." + id: github:repo_delete + severity: Critical +- from: github + test: gh\s+repo\s+archive(\s|$) + description: "Archiving a repository makes it read-only and can break workflows." + id: github:repo_archive + severity: High +- from: github + test: gh\s+repo\s+rename(\s|$) + description: "Renaming a repository breaks all existing URLs, clones, and CI/CD references." + id: github:repo_rename + severity: High +- from: github + test: gh\s+repo\s+edit\s+.*--visibility + description: "Changing repository visibility can expose private code or break public access." + id: github:repo_change_visibility + severity: High +- from: github + test: gh\s+release\s+delete(\s|$) + description: "Deleting a release removes it and its assets permanently." + id: github:release_delete + severity: High +- from: github + test: gh\s+secret\s+delete(\s|$) + description: "Deleting a secret can break CI/CD pipelines that depend on it." + id: github:secret_delete + severity: High +- from: github + test: gh\s+variable\s+delete(\s|$) + description: "Deleting a variable can break CI/CD pipelines that depend on it." + id: github:variable_delete + severity: High diff --git a/shellfirm/checks/heroku.yaml b/shellfirm/checks/heroku.yaml index 27dad6b..8fa1f6e 100644 --- a/shellfirm/checks/heroku.yaml +++ b/shellfirm/checks/heroku.yaml @@ -1,95 +1,95 @@ - from: heroku - test: heroku\s*ps:restart + test: heroku\s+ps:restart description: "Restarting app dynos" id: heroku:restart_app_dynos severity: Medium - from: heroku - test: heroku\s*ps:stop + test: heroku\s+ps:stop description: "Stop app dynos" id: heroku:stop_app_dynos severity: High - from: heroku - test: heroku\s*ps:kill + test: heroku\s+ps:kill description: "Kill app dynos" id: heroku:kill_app_dynos severity: High - from: heroku - test: heroku\s*maintenance:on + test: heroku\s+maintenance:on description: "Put the app into maintenance mode?" id: heroku:enable_maintenance_mode severity: High - from: heroku - test: heroku\s*members:remove + test: heroku\s+members:remove description: "Removes a user from a team" id: heroku:remove_member severity: High - from: heroku - test: heroku\s*features:disable + test: heroku\s+features:disable description: "Disables an app feature" id: heroku:disable_app_feature severity: Medium - from: heroku - test: heroku\s*container:rm + test: heroku\s+container:rm description: "Remove the process type from your app" id: heroku:remove_app_container severity: High - from: heroku - test: heroku\s*config:unset + test: heroku\s+config:unset description: "unset one or more config vars" id: heroku:unset_environment_variable severity: Medium - from: heroku - test: heroku\s*clients:destroy + test: heroku\s+clients:destroy description: "Delete client by ID" id: heroku:destroy_client severity: High - from: heroku - test: heroku\s*clients:rotate + test: heroku\s+clients:rotate description: "Rotate OAuth client secret" id: heroku:rotate_oauth_client severity: Medium - from: heroku - test: heroku\s*clients:update + test: heroku\s+clients:update description: "Update OAuth client" id: heroku:update_oauth_client severity: Medium - from: heroku - test: heroku\s*apps:destroy + test: heroku\s+apps:destroy description: "Permanently destroy an app" id: heroku:destroy_app severity: Critical - from: heroku - test: heroku\s*apps:leave + test: heroku\s+apps:leave description: "Remove yourself from a team app" id: heroku:remove_yourself_from_app severity: Medium - from: heroku - test: heroku\s*apps:rename + test: heroku\s+apps:rename description: "Rename an app" id: heroku:rename_app_name severity: Medium - from: heroku - test: heroku\s*addons:destroy + test: heroku\s+addons:destroy description: "Permanently destroy an add-on resource" id: heroku:destroy_addons severity: High - from: heroku - test: heroku\s*addons:detach + test: heroku\s+addons:detach description: "Detach an existing add-on resource from an app" id: heroku:detach_addon severity: Medium - from: heroku - test: heroku\s*access:remove + test: heroku\s+access:remove description: "Remove users from a team app" id: heroku:remove_user_access severity: High - from: heroku - test: heroku\s*access:update + test: heroku\s+access:update description: "Update existing collaborators on an team app" id: heroku:update_collaborators_access severity: Medium - from: heroku - test: heroku\s*repo:reset + test: heroku\s+repo:reset description: "Reset heroku repo" id: heroku:reset_repo severity: High diff --git a/shellfirm/checks/kubernetes.yaml b/shellfirm/checks/kubernetes.yaml index 96ee167..3b86ed1 100644 --- a/shellfirm/checks/kubernetes.yaml +++ b/shellfirm/checks/kubernetes.yaml @@ -1,5 +1,5 @@ - from: kubernetes - test: (kubectl|k)\s+delete\s+(ns|namespace) + test: (kubectl|k)\s+(?:-\S+\s+\S+\s+)*delete\s+(ns|namespace) description: "Deleting the namespace also deletes all the residing components." id: kubernetes:delete_namespace severity: Critical @@ -9,7 +9,7 @@ alternative: "kubectl get all -n " alternative_info: "List all resources in the namespace first to verify what will be deleted." - from: kubernetes - test: (k|kubectl)\s+delete\s+ + test: (k|kubectl)\s+(?:-\S+\s+\S+\s+)*delete\s+ description: "This command will going to delete a given resource." id: kubernetes-strict:delete_resource severity: High @@ -73,3 +73,23 @@ filters: - type: NotContains value: "--dry-run" +- from: kubernetes + test: (kubectl|k)\s+(?:-\S+\s+\S+\s+)*delete\s+.*--all(\s|$) + description: "Deleting all resources of a type wipes everything in the namespace." + id: kubernetes:delete_all_resources + severity: Critical + filters: + - type: NotContains + value: "--dry-run" + alternative: "kubectl get -n " + alternative_info: "List resources first to verify what would be deleted." +- from: kubernetes + test: (kubectl|k)\s+(?:-\S+\s+\S+\s+)*apply\s+.*--force(\s|$) + description: "Force-applying deletes and recreates resources, causing downtime." + id: kubernetes:apply_force + severity: High + filters: + - type: NotContains + value: "--dry-run" + alternative: "kubectl apply -f " + alternative_info: "Apply without --force to update resources in-place without downtime." diff --git a/shellfirm/checks/mongodb.yaml b/shellfirm/checks/mongodb.yaml index 5a2a309..2724383 100644 --- a/shellfirm/checks/mongodb.yaml +++ b/shellfirm/checks/mongodb.yaml @@ -1,11 +1,11 @@ - from: mongodb - test: (?i)mongosh?.*--eval.*drop + test: (?i)mongo(?:sh)?.*--eval.*drop description: "Executing drop operations via MongoDB shell." id: mongodb:drop severity: Critical # Interactive session patterns (for shellfirm wrap) - from: mongodb - test: (?i)db\.\w+\.drop\(\) + test: (?i)db\.[\w-]+\.drop\(\) id: mongodb:interactive_drop_collection severity: Critical description: "Dropping a MongoDB collection will permanently delete all its data." diff --git a/shellfirm/checks/mysql.yaml b/shellfirm/checks/mysql.yaml index 6920ff8..67ba9f6 100644 --- a/shellfirm/checks/mysql.yaml +++ b/shellfirm/checks/mysql.yaml @@ -1,5 +1,5 @@ - from: mysql - test: (?i)mysql.*-e\s+.*DROP + test: (?i)mysql.*-e\s+.*\bDROP\s description: "Executing DROP commands via mysql CLI." id: mysql:drop severity: Critical diff --git a/shellfirm/checks/netlify.yaml b/shellfirm/checks/netlify.yaml new file mode 100644 index 0000000..b71c9ff --- /dev/null +++ b/shellfirm/checks/netlify.yaml @@ -0,0 +1,15 @@ +- from: netlify + test: netlify\s+sites:delete(\s|$) + description: "Deleting a Netlify site permanently removes it and all its deployments." + id: netlify:sites_delete + severity: Critical +- from: netlify + test: netlify\s+env:unset\s+ + description: "Removing an environment variable can break builds and deployments." + id: netlify:env_unset + severity: High +- from: netlify + test: netlify\s+env:clone\s+ + description: "Cloning environment variables overwrites the target site's variables." + id: netlify:env_clone + severity: High diff --git a/shellfirm/checks/network.yaml b/shellfirm/checks/network.yaml index 2e97b2b..f47adc0 100644 --- a/shellfirm/checks/network.yaml +++ b/shellfirm/checks/network.yaml @@ -1,5 +1,5 @@ - from: network - test: iptables\s+-F + test: ip6?tables\s+-F description: "Flushing all firewall rules" id: network:flush_iptables severity: Critical @@ -14,7 +14,7 @@ id: network:flush_nat_rules severity: High - from: network - test: ufw\s+disable + test: ufw\s+disable(\s|$) description: "Disabling firewall" id: network:disable_firewall severity: Critical @@ -39,7 +39,7 @@ id: network:bring_down_interface severity: High - from: network - test: ip\s+link\s+set\s+\w+\s+down + test: ip\s+link\s+set\s+[\w-]+\s+down description: "Bringing down network interface using ip command" id: network:bring_down_interface_ip severity: High @@ -48,3 +48,13 @@ description: "Deleting default route" id: network:delete_default_route severity: High +- from: network + test: nft\s+flush\s+ruleset + description: "Flushing all nftables rules removes all firewall protection." + id: network:flush_nftables + severity: Critical +- from: network + test: ip\s+route\s+flush + description: "Flushing the routing table causes immediate loss of network connectivity." + id: network:flush_routes + severity: Critical diff --git a/shellfirm/checks/npm.yaml b/shellfirm/checks/npm.yaml new file mode 100644 index 0000000..fffb6ac --- /dev/null +++ b/shellfirm/checks/npm.yaml @@ -0,0 +1,20 @@ +- from: npm + test: (^|\s)npm\s+unpublish(\s|$) + description: "Unpublishing a package removes it from the registry, breaking all downstream dependents." + id: npm:unpublish + severity: Critical +- from: npm + test: (^|\s)npm\s+deprecate\s+ + description: "Deprecating a package marks it as deprecated for all users." + id: npm:deprecate + severity: High +- from: npm + test: yarn\s+npm\s+unpublish(\s|$) + description: "Unpublishing a package removes it from the registry, breaking all downstream dependents." + id: npm:yarn_unpublish + severity: Critical +- from: npm + test: (^|\s)pnpm\s+unpublish(\s|$) + description: "Unpublishing a package removes it from the registry, breaking all downstream dependents." + id: npm:pnpm_unpublish + severity: Critical diff --git a/shellfirm/checks/psql.yaml b/shellfirm/checks/psql.yaml index 62fe58c..d945713 100644 --- a/shellfirm/checks/psql.yaml +++ b/shellfirm/checks/psql.yaml @@ -1,5 +1,5 @@ - from: psql - test: (?i)psql.*-c\s+.*DROP + test: (?i)psql.*-c\s+.*\bDROP\s description: "Executing DROP commands via psql CLI." id: psql:drop severity: Critical diff --git a/shellfirm/checks/redis.yaml b/shellfirm/checks/redis.yaml index 4286881..f99170a 100644 --- a/shellfirm/checks/redis.yaml +++ b/shellfirm/checks/redis.yaml @@ -1,12 +1,12 @@ - from: redis - test: (?i)redis-cli\s+FLUSHALL + test: (?i)redis-cli\s+.*FLUSHALL description: "FLUSHALL will delete all data across all Redis databases." id: redis:flushall severity: Critical alternative: "redis-cli FLUSHDB" alternative_info: "FLUSHDB only clears the current database, not all databases." - from: redis - test: (?i)redis-cli\s+FLUSHDB + test: (?i)redis-cli\s+.*FLUSHDB description: "FLUSHDB will delete all data in the current Redis database." id: redis:flushdb severity: High diff --git a/shellfirm/checks/shell.yaml b/shellfirm/checks/shell.yaml index 55e0867..1de7a8b 100644 --- a/shellfirm/checks/shell.yaml +++ b/shellfirm/checks/shell.yaml @@ -1,21 +1,28 @@ - from: shell - test: 'curl\s+.*\|\s*(bash|sh|zsh|fish|source\s)' + test: 'curl\s+.*\|\s*(?:sudo\s+)?(bash|sh|zsh|fish|source\s)' description: "Piping remote content directly to shell executes arbitrary code without inspection." id: shell:curl_pipe_to_shell severity: High alternative: "curl -o script.sh && cat script.sh && bash script.sh" alternative_info: "Download first, review the script, then execute it." - from: shell - test: 'wget\s+.*-O\s*-\s.*\|\s*(bash|sh|zsh|fish)' + test: 'wget\s+.*-\S*O\s*-\s.*\|\s*(?:sudo\s+)?(bash|sh|zsh|fish)' description: "Piping downloaded content to shell executes arbitrary code without inspection." id: shell:wget_pipe_to_shell severity: High alternative: "wget -O script.sh && cat script.sh && bash script.sh" alternative_info: "Download first, review the script, then execute it." - from: shell - test: 'eval\s+"\$\(curl' + test: 'eval\s+"?\$\(curl' description: "Evaluating remote content executes arbitrary code without inspection." id: shell:eval_curl severity: High alternative: "curl -o script.sh && cat script.sh && source script.sh" alternative_info: "Download first, review the script, then source it." +- from: shell + test: 'curl\s+.*\|\s*(?:sudo\s+)?(python3?|perl|ruby)\b' + description: "Piping remote content to an interpreter executes arbitrary code without inspection." + id: shell:curl_pipe_to_interpreter + severity: High + alternative: "curl -o script.py && cat script.py && python script.py" + alternative_info: "Download first, review the script, then execute it." diff --git a/shellfirm/checks/terraform.yaml b/shellfirm/checks/terraform.yaml index b2bd1f1..5ad65c5 100644 --- a/shellfirm/checks/terraform.yaml +++ b/shellfirm/checks/terraform.yaml @@ -7,6 +7,15 @@ severity: Critical alternative: "terraform plan -out=plan.tfplan && terraform apply plan.tfplan" alternative_info: "Review the plan first, then apply from the saved plan file for a controlled deployment." +- from: terraform + test: terraform\s+destroy\s+.*-auto-approve + method: Regex + enable: true + description: "This command will destroy all managed infrastructure without asking for confirmation." + id: terraform:destroy_auto_approve + severity: Critical + alternative: "terraform plan -destroy" + alternative_info: "Preview what would be destroyed first, then run 'terraform destroy' with manual approval." - from: terraform test: terraform\s+state\s+(mv|replace-provider) method: Regex @@ -19,6 +28,15 @@ value: "-dry-run" alternative: "terraform state -dry-run" alternative_info: "Preview the state change with -dry-run before actually modifying state." +- from: terraform + test: terraform\s+state\s+rm\s + method: Regex + enable: true + description: "Removing a resource from state causes Terraform to lose track of existing infrastructure." + id: terraform:state_rm + severity: High + alternative: "terraform state list" + alternative_info: "List resources in state first to verify what you're removing." - from: terraform test: terraform\s+workspace\s+delete.*(-force) method: Regex @@ -34,7 +52,7 @@ id: terraform:workspace_delete_without_lock severity: High - from: terraform - test: terraform\s+force-unlock\s+(-force) + test: terraform\s+force-unlock\s+.*(-force) method: Regex enable: true description: "Manually unlock the state for the defined configuration. without asking for confirmation." diff --git a/shellfirm/checks/vercel.yaml b/shellfirm/checks/vercel.yaml new file mode 100644 index 0000000..94940cf --- /dev/null +++ b/shellfirm/checks/vercel.yaml @@ -0,0 +1,20 @@ +- from: vercel + test: vercel\s+(remove|rm)(\s|$) + description: "Removing a Vercel deployment or project takes it offline permanently." + id: vercel:remove + severity: Critical +- from: vercel + test: vercel\s+project\s+(remove|rm)(\s|$) + description: "Removing a Vercel project deletes it and all its deployments." + id: vercel:project_remove + severity: Critical +- from: vercel + test: vercel\s+env\s+(rm|remove)\s+ + description: "Removing an environment variable can break deployments that depend on it." + id: vercel:env_remove + severity: High +- from: vercel + test: vercel\s+domains?\s+(rm|remove)(\s|$) + description: "Removing a domain disconnects it from your deployment." + id: vercel:domain_remove + severity: High diff --git a/shellfirm/src/bin/cmd/snapshots/shellfirm__cmd__config__test_config_cli_command__reset_config_restores_defaults.snap b/shellfirm/src/bin/cmd/snapshots/shellfirm__cmd__config__test_config_cli_command__reset_config_restores_defaults.snap index bf849f4..fd70422 100644 --- a/shellfirm/src/bin/cmd/snapshots/shellfirm__cmd__config__test_config_cli_command__reset_config_restores_defaults.snap +++ b/shellfirm/src/bin/cmd/snapshots/shellfirm__cmd__config__test_config_cli_command__reset_config_restores_defaults.snap @@ -12,17 +12,22 @@ Ok( "base", "database", "docker", + "flyio", "fs", "gcp", "git", + "github", "heroku", "kubernetes", "mongodb", "mysql", + "netlify", "network", + "npm", "psql", "redis", "terraform", + "vercel", ], disabled_groups: [], ignores_patterns_ids: [], diff --git a/shellfirm/src/config.rs b/shellfirm/src/config.rs index 5d48754..0c0173a 100644 --- a/shellfirm/src/config.rs +++ b/shellfirm/src/config.rs @@ -213,23 +213,28 @@ const fn default_blast_radius() -> bool { true } -pub const DEFAULT_ENABLED_GROUPS: [&str; 16] = [ +pub const DEFAULT_ENABLED_GROUPS: [&str; 21] = [ "aws", "azure", "base", "database", "docker", + "flyio", "fs", "gcp", "git", + "github", "heroku", "kubernetes", "mongodb", "mysql", + "netlify", "network", + "npm", "psql", "redis", "terraform", + "vercel", ]; /// The user challenge when user need to confirm the command. @@ -751,17 +756,22 @@ mod test_settings { "base", "database", "docker", + "flyio", "fs", "gcp", "git", + "github", "heroku", "kubernetes", "mongodb", "mysql", + "netlify", "network", + "npm", "psql", "redis", "terraform", + "vercel", ] ); } diff --git a/shellfirm/tests/checks/aws.yaml b/shellfirm/tests/checks/aws.yaml index 450f3b3..74e0e8a 100644 --- a/shellfirm/tests/checks/aws.yaml +++ b/shellfirm/tests/checks/aws.yaml @@ -215,3 +215,26 @@ - test: "awss ecs delete-service --cluster test --service test" description: "invalid command" expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- aws:iam_delete edge cases -- +- test: "aws iam delete-user-policy --user-name test --policy-name test" + description: "negative: delete-user-policy should not match delete-user" + expect_ids: [] +- test: "aws iam delete-role-policy --role-name test --policy-name test" + description: "negative: delete-role-policy should not match delete-role" + expect_ids: [] +- test: "aws iam delete-group-policy --group-name test --policy-name test" + description: "negative: delete-group-policy should not match delete-group" + expect_ids: [] + +# -- aws:ec2_terminate edge cases -- +- test: "aws --profile prod ec2 terminate-instances --instance-ids i-123" + description: "match ec2 terminate with --profile flag before subcommand" + expect_ids: ["aws:ec2_terminate"] + +# -- aws:s3_recursive_delete edge cases -- +- test: "aws s3 rm s3://mybucket/ --recursive --dryrun" + description: "EDGE: dryrun should be filtered out" + expect_ids: [] diff --git a/shellfirm/tests/checks/base.yaml b/shellfirm/tests/checks/base.yaml index 8805c7d..9aad131 100644 --- a/shellfirm/tests/checks/base.yaml +++ b/shellfirm/tests/checks/base.yaml @@ -184,3 +184,77 @@ - test: "ssh-keygen -t rsa" description: "negative: generating key should not match" expect_ids: [] + +# -- base:poweroff_machine -- +- test: "halt" + description: "match halt command" + expect_ids: ["base:poweroff_machine"] +- test: "poweroff" + description: "match poweroff command" + expect_ids: ["base:poweroff_machine"] +- test: "halt -f" + description: "match halt with flag" + expect_ids: ["base:poweroff_machine"] +- test: "poweroff -f" + description: "match poweroff with flag" + expect_ids: ["base:poweroff_machine"] +- test: "halting" + description: "negative: word starting with halt should not match" + expect_ids: [] +- test: "powerofff" + description: "negative: misspelled poweroff should not match" + expect_ids: [] + +# -- base:init_shutdown_reboot -- +- test: "init 0" + description: "match init 0 (shutdown)" + expect_ids: ["base:init_shutdown_reboot"] +- test: "init 6" + description: "match init 6 (reboot)" + expect_ids: ["base:init_shutdown_reboot"] +- test: "init 0" + description: "match init 0 with extra spaces" + expect_ids: ["base:init_shutdown_reboot"] +- test: "init 1" + description: "negative: init 1 (single user) should not match" + expect_ids: [] +- test: "init 3" + description: "negative: init 3 (multi-user) should not match" + expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- base:reboot_machine edge cases -- +- test: "sudo reboot" + description: "EDGE: sudo reboot" + expect_ids: ["base:reboot_machine"] +- test: "myreboot" + description: "negative: substring should not match - reboot requires word boundary" + expect_ids: [] + +# -- base:shutdown_machine edge cases -- +- test: "sudo shutdown -h now" + description: "EDGE: sudo shutdown with flags" + expect_ids: ["base:shutdown_machine"] +- test: "myshutdown" + description: "negative: substring should not match - shutdown requires word boundary" + expect_ids: [] + +# -- systemd:stop_critical_service edge cases -- +- test: "systemctl stop docker.service" + description: "match stopping docker with .service suffix" + expect_ids: ["systemd:stop_critical_service"] +- test: "systemctl stop redis-server" + description: "match stopping redis-server variant" + expect_ids: ["systemd:stop_critical_service"] +- test: "systemctl stop postgresql-14" + description: "match stopping postgresql with version suffix" + expect_ids: ["systemd:stop_critical_service"] + +# -- base:delete_all_cron_tasks edge cases -- +- test: "sudo crontab -r" + description: "EDGE: sudo prefix" + expect_ids: ["base:delete_all_cron_tasks"] +- test: "crontab -l" + description: "EDGE: list should not match" + expect_ids: [] diff --git a/shellfirm/tests/checks/database.yaml b/shellfirm/tests/checks/database.yaml index 8c42f93..798f4a8 100644 --- a/shellfirm/tests/checks/database.yaml +++ b/shellfirm/tests/checks/database.yaml @@ -125,3 +125,60 @@ - test: "ALTER TABLE users ADD COLUMN email VARCHAR(255)" description: "negative: ADD COLUMN should not match" expect_ids: [] +- test: "ALTER TABLE public.users DROP COLUMN email" + description: "match schema-qualified table in ALTER TABLE DROP COLUMN" + expect_ids: ["database:alter_drop_column"] + +# -- database:drop_index -- +- test: "DROP INDEX idx_users_email" + description: "match DROP INDEX" + expect_ids: ["database:drop_index"] +- test: "drop index idx_users_email" + description: "match lowercase drop index" + expect_ids: ["database:drop_index"] +- test: "DROP INDEX idx_orders_date ON orders" + description: "match DROP INDEX with ON clause" + expect_ids: ["database:drop_index"] +- test: "DROP INDEX IF EXISTS idx_users_email" + description: "match DROP INDEX IF EXISTS" + expect_ids: ["database:drop_index"] +- test: "CREATE INDEX idx_users_email ON users(email)" + description: "negative: CREATE INDEX should not match" + expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- database:delete_all_rows edge cases -- +- test: "DELETE FROM users;" + description: "EDGE: extra spaces in DELETE" + expect_ids: ["database:delete_all_rows"] +- test: "DELETE FROM _temp_table;" + description: "EDGE: underscore in table name" + expect_ids: ["database:delete_all_rows"] +- test: "DELETE FROM table123;" + description: "EDGE: numbers in table name" + expect_ids: ["database:delete_all_rows"] +- test: "DELETE FROM users RETURNING *;" + description: "EDGE: RETURNING clause should not match delete_all (has content after table)" + expect_ids: [] + +# -- database:update_all_rows edge cases -- +- test: "UPDATE users SET active=false" + description: "EDGE: UPDATE without semicolon" + expect_ids: ["database:update_all_rows"] +- test: "UPDATE users SET active=false, name='test';" + description: "EDGE: multiple SET columns" + expect_ids: ["database:update_all_rows"] + +# -- database:drop_schema_role_user edge cases -- +- test: "DROP SCHEMA IF EXISTS public CASCADE" + description: "EDGE: IF EXISTS with CASCADE" + expect_ids: ["database:drop_schema_role_user"] +- test: "DROP ROLE IF EXISTS myrole;" + description: "EDGE: IF EXISTS for role" + expect_ids: ["database:drop_schema_role_user"] + +# -- database:drop_index edge cases -- +- test: "DROP INDEX CONCURRENTLY idx_users_email;" + description: "EDGE: CONCURRENTLY keyword (PostgreSQL)" + expect_ids: ["database:drop_index"] diff --git a/shellfirm/tests/checks/docker.yaml b/shellfirm/tests/checks/docker.yaml index f5c2ab5..d6cb652 100644 --- a/shellfirm/tests/checks/docker.yaml +++ b/shellfirm/tests/checks/docker.yaml @@ -203,4 +203,50 @@ expect_ids: ["docker:buildx_prune_all"] - test: "docker buildx prune" description: "negative: buildx prune without --all should not match" - expect_ids: [] \ No newline at end of file + expect_ids: [] + +# -- docker:run_privileged -- +- test: "docker run --privileged nginx" + description: "match run with --privileged" + expect_ids: ["docker:run_privileged"] +- test: "docker run -it --privileged ubuntu bash" + description: "match run with flags before --privileged" + expect_ids: ["docker:run_privileged"] +- test: "docker run --name mycontainer --privileged nginx" + description: "match run with --name before --privileged" + expect_ids: ["docker:run_privileged"] +- test: "docker run --privileged nginx" + description: "match run with extra spaces" + expect_ids: ["docker:run_privileged"] +- test: "docker run --privileged=true nginx" + description: "match run with --privileged=true" + expect_ids: ["docker:run_privileged"] +- test: "docker run --privileged=false nginx" + description: "negative: --privileged=false should not match" + expect_ids: [] +- test: "docker run nginx" + description: "negative: run without --privileged should not match" + expect_ids: [] +- test: "docker run -it ubuntu bash" + description: "negative: run with regular flags should not match" + expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- docker:compose_down_volumes edge cases -- +- test: "docker compose -f custom.yml down -v" + description: "match compose with -f flag before down with -v" + expect_ids: ["docker:compose_down_volumes"] +- test: "docker-compose -f docker-compose.prod.yml down --volumes" + description: "match compose with -f flag before down with --volumes" + expect_ids: ["docker:compose_down_volumes"] + +# -- docker:force_remove_all_containers edge cases -- +- test: "docker rm -f `docker ps -aq`" + description: "match force remove all containers with backtick syntax" + expect_ids: ["docker:force_remove_all_containers"] + +# -- docker:run_privileged edge cases -- +- test: "docker run --security-opt seccomp=unconfined --privileged nginx" + description: "EDGE: long option before --privileged" + expect_ids: ["docker:run_privileged"] \ No newline at end of file diff --git a/shellfirm/tests/checks/flyio.yaml b/shellfirm/tests/checks/flyio.yaml new file mode 100644 index 0000000..4289575 --- /dev/null +++ b/shellfirm/tests/checks/flyio.yaml @@ -0,0 +1,164 @@ +--- +# -- flyio:apps_destroy -- +- test: "fly apps destroy" + description: "match fly apps destroy" + expect_ids: ["flyio:apps_destroy"] +- test: "fly apps destroy my-app" + description: "match fly apps destroy with app name" + expect_ids: ["flyio:apps_destroy"] +- test: "fly apps destroy --yes" + description: "match fly apps destroy with --yes" + expect_ids: ["flyio:apps_destroy"] +- test: "flyctl apps destroy my-app" + description: "match flyctl (full binary name) apps destroy" + expect_ids: ["flyio:apps_destroy"] +- test: "fly apps destroy" + description: "match fly apps destroy with extra spaces" + expect_ids: ["flyio:apps_destroy"] +- test: "fly apps list" + description: "negative: apps list should not match" + expect_ids: [] +- test: "fly apps create my-app" + description: "negative: apps create should not match" + expect_ids: [] +# edge case: "flyy" should not match fly(?:ctl)? — "flyy" is not "fly" or "flyctl" +- test: "flyy apps destroy my-app" + description: "negative: flyy (typo) should not match — fly(?:ctl)? won't match flyy" + expect_ids: [] +# edge case: "flyctl" full form with extra args +- test: "flyctl apps destroy my-app --yes" + description: "match flyctl apps destroy with app name and --yes" + expect_ids: ["flyio:apps_destroy"] +# edge case: sudo prefix +- test: "sudo fly apps destroy my-app" + description: "match fly apps destroy with sudo prefix" + expect_ids: ["flyio:apps_destroy"] +# edge case: "fly apps destroyy" — typo after destroy, (\s|$) should prevent match +- test: "fly apps destroyy" + description: "negative: destroyy typo should not match" + expect_ids: [] +# edge case: "fly apps destroy" at end of string (boundary check with $) +- test: "flyctl apps destroy" + description: "match flyctl apps destroy at end of string" + expect_ids: ["flyio:apps_destroy"] +# edge case: "flyctle" (extra char after flyctl) — fly(?:ctl)? would match "flyctl" portion but "e" follows +# The regex is fly(?:ctl)?\s+ so "flyctle" has no \s after, it has "e" — so "flyctle\s+apps" would mean +# fly(?:ctl)? matches "flyctl" and then "e\s+" must match \s+ which fails. So no match. +- test: "flyctle apps destroy my-app" + description: "negative: flyctle (extra char) should not match" + expect_ids: [] + +# -- flyio:secrets_unset -- +- test: "fly secrets unset MY_SECRET" + description: "match fly secrets unset" + expect_ids: ["flyio:secrets_unset"] +- test: "fly secrets unset SECRET1 SECRET2" + description: "match fly secrets unset with multiple secrets" + expect_ids: ["flyio:secrets_unset"] +- test: "flyctl secrets unset MY_SECRET" + description: "match flyctl secrets unset" + expect_ids: ["flyio:secrets_unset"] +- test: "fly secrets unset MY_SECRET --app my-app" + description: "match fly secrets unset with --app flag" + expect_ids: ["flyio:secrets_unset"] +- test: "fly secrets unset MY_SECRET" + description: "match fly secrets unset with extra spaces" + expect_ids: ["flyio:secrets_unset"] +- test: "fly secrets list" + description: "negative: secrets list should not match" + expect_ids: [] +- test: "fly secrets set MY_SECRET=value" + description: "negative: secrets set should not match" + expect_ids: [] +# edge case: "fly secrets unset" with no secret name — regex requires trailing \s+ so bare command won't match +- test: "fly secrets unset" + description: "negative: fly secrets unset with no trailing content should not match (regex requires trailing \\s+)" + expect_ids: [] +# edge case: flyctl form for secrets unset with extra spaces +- test: "flyctl secrets unset SECRET1 SECRET2" + description: "match flyctl secrets unset with extra spaces and multiple secrets" + expect_ids: ["flyio:secrets_unset"] +# edge case: sudo prefix +- test: "sudo fly secrets unset MY_SECRET" + description: "match fly secrets unset with sudo prefix" + expect_ids: ["flyio:secrets_unset"] +# edge case: "flyy secrets unset" — should not match +- test: "flyy secrets unset MY_SECRET" + description: "negative: flyy (typo) should not match secrets_unset" + expect_ids: [] + +# -- flyio:volumes_destroy -- +- test: "fly volumes destroy" + description: "match fly volumes destroy" + expect_ids: ["flyio:volumes_destroy"] +- test: "fly volumes destroy vol_abc123" + description: "match fly volumes destroy with volume ID" + expect_ids: ["flyio:volumes_destroy"] +- test: "fly volume destroy vol_abc123" + description: "match fly volume (singular) destroy" + expect_ids: ["flyio:volumes_destroy"] +- test: "flyctl volumes destroy vol_abc123" + description: "match flyctl volumes destroy" + expect_ids: ["flyio:volumes_destroy"] +- test: "fly volumes destroy" + description: "match fly volumes destroy with extra spaces" + expect_ids: ["flyio:volumes_destroy"] +- test: "fly volumes list" + description: "negative: volumes list should not match" + expect_ids: [] +- test: "fly volumes create" + description: "negative: volumes create should not match" + expect_ids: [] +# edge case: "fly volumes destroyy" — typo, (\s|$) should prevent match +- test: "fly volumes destroyy" + description: "negative: destroyy typo should not match volumes_destroy" + expect_ids: [] +# edge case: flyctl volume (singular) destroy — regex is volumes? so both match +- test: "flyctl volume destroy vol_abc123" + description: "match flyctl volume (singular) destroy" + expect_ids: ["flyio:volumes_destroy"] +# edge case: "fly volumess destroy" — regex is volumes? (s is optional), "volumess" has double-s. volumes? matches "volumes" then extra "s" is left before space +# Actually "volumess" — regex `volumes?` matches "volume" + optional "s" = "volumes", leaving "s destroy". Then regex needs \s+destroy. "s destroy" won't match \s+destroy. So no match. +- test: "fly volumess destroy" + description: "negative: volumess (typo) should not match" + expect_ids: [] +# edge case: sudo prefix with volume singular +- test: "sudo fly volume destroy vol_123" + description: "match fly volume destroy with sudo prefix" + expect_ids: ["flyio:volumes_destroy"] + +# -- flyio:postgres_destroy -- +- test: "fly postgres destroy" + description: "match fly postgres destroy" + expect_ids: ["flyio:postgres_destroy"] +- test: "fly postgres destroy my-pg-cluster" + description: "match fly postgres destroy with cluster name" + expect_ids: ["flyio:postgres_destroy"] +- test: "flyctl postgres destroy my-pg-cluster" + description: "match flyctl postgres destroy" + expect_ids: ["flyio:postgres_destroy"] +- test: "fly postgres destroy" + description: "match fly postgres destroy with extra spaces" + expect_ids: ["flyio:postgres_destroy"] +- test: "fly postgres list" + description: "negative: postgres list should not match" + expect_ids: [] +- test: "fly postgres create" + description: "negative: postgres create should not match" + expect_ids: [] +# edge case: "fly postgres destroyy" — typo, (\s|$) prevents match +- test: "fly postgres destroyy" + description: "negative: destroyy typo should not match postgres_destroy" + expect_ids: [] +# edge case: sudo flyctl postgres destroy +- test: "sudo flyctl postgres destroy my-db" + description: "match flyctl postgres destroy with sudo prefix" + expect_ids: ["flyio:postgres_destroy"] +# edge case: "flyy postgres destroy" — should not match +- test: "flyy postgres destroy my-db" + description: "negative: flyy (typo) should not match postgres_destroy" + expect_ids: [] +# edge case: "fly postgres destroy" at end of string +- test: "flyctl postgres destroy" + description: "match flyctl postgres destroy at end of string" + expect_ids: ["flyio:postgres_destroy"] diff --git a/shellfirm/tests/checks/fs.yaml b/shellfirm/tests/checks/fs.yaml index 504f9e2..b0cf231 100644 --- a/shellfirm/tests/checks/fs.yaml +++ b/shellfirm/tests/checks/fs.yaml @@ -1384,3 +1384,65 @@ - test: "chown user:group file.txt" description: "negative: chown without -R should not match" expect_ids: [] + +# -- fs:shred -- +- test: "shred secret.txt" + description: "match shred on a file" + expect_ids: ["fs:shred"] +- test: "shred -vfz secret.txt" + description: "match shred with flags" + expect_ids: ["fs:shred"] +- test: "shred -n 10 secret.txt" + description: "match shred with pass count" + expect_ids: ["fs:shred"] +- test: "shred -v secret.txt" + description: "match shred with extra spaces" + expect_ids: ["fs:shred"] + +# -- fs:truncate_zero -- +- test: "truncate -s 0 file.txt" + description: "match truncate to zero" + expect_ids: ["fs:truncate_zero"] +- test: "truncate -s0 file.txt" + description: "match truncate to zero without space" + expect_ids: ["fs:truncate_zero"] +- test: "truncate -s 0 file.txt" + description: "match truncate with extra spaces" + expect_ids: ["fs:truncate_zero"] +- test: "truncate --size 0 file.txt" + description: "negative: long form --size not matched by -s pattern" + expect_ids: [] +- test: "truncate -s 1024 file.txt" + description: "negative: truncate to non-zero size should not match" + expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- fs:recursively_delete edge cases -- +- test: "rm --force --recursive /" + description: "EDGE: all long-form flags should still match" + expect_ids: ["fs:recursively_delete", "fs-strict:any_deletion"] +- test: "rm -rf ./*" + description: "match rm -rf ./* as dangerous recursive delete" + expect_ids: ["fs:recursively_delete", "fs-strict:any_deletion"] + +# -- fs:rsync_delete edge cases -- +- test: "rsync -avz --delete-after /src/ /dest/" + description: "negative: --delete-after should not match --delete" + expect_ids: [] +- test: "rsync -avz --delete-excluded /src/ /dest/" + description: "negative: --delete-excluded should not match --delete" + expect_ids: [] + +# -- fs:recursively_chown edge cases -- +- test: "chown -Rv root:root /" + description: "EDGE: combined -Rv flag on root" + expect_ids: ["fs:recursively_chown"] +- test: "chown -R user:group .." + description: "EDGE: parent dir .. should match" + expect_ids: ["fs:recursively_chown"] + +# -- fs-strict:any_deletion edge cases -- +- test: "rm --recursive --force file.txt" + description: "EDGE: long-form --recursive --force flags" + expect_ids: ["fs-strict:any_deletion"] diff --git a/shellfirm/tests/checks/git.yaml b/shellfirm/tests/checks/git.yaml index 855b895..33c6f3d 100644 --- a/shellfirm/tests/checks/git.yaml +++ b/shellfirm/tests/checks/git.yaml @@ -433,3 +433,115 @@ - test: "git reflog expire --expire=90.days" description: "negative: normal expire should not match" expect_ids: [] + +# -- git:stash_clear -- +- test: "git stash clear" + description: "match stash clear" + expect_ids: ["git:stash_clear"] +- test: "git stash clear" + description: "match stash clear with extra spaces" + expect_ids: ["git:stash_clear"] +- test: "git stash list" + description: "negative: stash list should not match" + expect_ids: [] +- test: "git stash pop" + description: "negative: stash pop should not match stash_clear" + expect_ids: ["git-strict:stash_pop_drop"] +- test: "git stash drop" + description: "negative: stash drop should not match stash_clear" + expect_ids: ["git-strict:stash_pop_drop"] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- git:force_push edge cases -- +- test: "git push origin feature --force-if-includes" + description: "force-if-includes is safe alternative and should not match" + expect_ids: [] +- test: "git push origin -f" + description: "EDGE: force push with extra spaces" + expect_ids: ["git:force_push"] +- test: "git push -f origin main" + description: "EDGE: -f immediately after push before remote" + expect_ids: ["git:force_push"] + +# -- git:gc_prune edge cases -- +- test: "git gc --aggressive --prune=now" + description: "match gc with intervening flags before --prune=now" + expect_ids: ["git:gc_prune"] + +# -- git:reflog_expire edge cases -- +- test: "git reflog expire --all --expire=now" + description: "match reflog expire with intervening flags before --expire=now" + expect_ids: ["git:reflog_expire"] + +# -- git:delete_all edge cases -- +- test: "git rm -f *" + description: "match rm with flag before wildcard" + expect_ids: ["git:delete_all", "fs:recursively_delete"] +- test: "git rm -r ." + description: "match rm with flag before dot" + expect_ids: ["git:delete_all", "fs:recursively_delete"] +- test: "git rm ." + description: "match rm dot with extra spaces" + expect_ids: ["git:delete_all"] +- test: "git rm .." + description: "negative: parent dir .. should not match delete all" + expect_ids: [] + +# -- git:clean_force edge cases -- +- test: "git clean --force -d" + description: "match clean with --force long-form" + expect_ids: ["git:clean_force"] +- test: "git clean -f -x -d" + description: "EDGE: three separate flags with -x between" + expect_ids: ["git:clean_force"] + +# -- git:force_checkout edge cases -- +- test: "git checkout main -f" + description: "match force checkout with -f after branch name" + expect_ids: ["git:force_checkout"] +- test: "git checkout --force" + description: "match force checkout with extra spaces" + expect_ids: ["git:force_checkout"] + +# -- git:interactive_rebase edge cases -- +- test: "git rebase --interactive HEAD~3" + description: "match interactive rebase with --interactive long-form" + expect_ids: ["git-strict:rebase", "git:interactive_rebase"] + +# -- git:delete_ref edge cases -- +- test: "git update-ref --delete refs/heads/main" + description: "match delete ref with --delete long-form" + expect_ids: ["git:delete_ref"] +- test: "git update-ref -d refs/stash" + description: "EDGE: extra spaces in delete ref" + expect_ids: ["git:delete_ref"] + +# -- git-strict:create_tag edge cases -- +- test: "git tag --annotate v1.0" + description: "match annotated tag with --annotate long-form" + expect_ids: ["git-strict:create_tag"] + +# -- git:push_delete_branch edge cases -- +- test: "git push --delete origin feature" + description: "match push delete with --delete before remote" + expect_ids: ["git:push_delete_branch"] +- test: "git push origin --delete feature" + description: "match push delete with extra spaces" + expect_ids: ["git:push_delete_branch"] +- test: "git push origin :refs/heads/feature" + description: "EDGE: full refspec delete" + expect_ids: ["git:push_delete_branch"] + +# -- git-strict:add_all edge cases -- +- test: "git add -p ." + description: "EDGE: -p before dot - should not match add_all" + expect_ids: [] +- test: "git add --all --verbose" + description: "EDGE: --all with trailing flag" + expect_ids: ["git-strict:add_all"] + +# -- git:stash_clear edge cases -- +- test: "git stash clearing" + description: "EDGE: word starting with clear should not match" + expect_ids: [] diff --git a/shellfirm/tests/checks/github.yaml b/shellfirm/tests/checks/github.yaml new file mode 100644 index 0000000..b38c621 --- /dev/null +++ b/shellfirm/tests/checks/github.yaml @@ -0,0 +1,161 @@ +--- +# -- github:repo_delete -- +- test: "gh repo delete" + description: "match repo delete" + expect_ids: ["github:repo_delete"] +- test: "gh repo delete myorg/myrepo" + description: "match repo delete with org/repo" + expect_ids: ["github:repo_delete"] +- test: "gh repo delete --yes" + description: "match repo delete with --yes confirmation skip" + expect_ids: ["github:repo_delete"] +- test: "gh repo delete myorg/myrepo --yes" + description: "match repo delete with org/repo and --yes" + expect_ids: ["github:repo_delete"] +- test: "gh repo delete" + description: "match repo delete with extra spaces" + expect_ids: ["github:repo_delete"] +- test: "gh repo deletee" + description: "negative: typo should not match" + expect_ids: [] +- test: "gh repo list" + description: "negative: repo list should not match" + expect_ids: [] +- test: "gh repo create myrepo" + description: "negative: repo create should not match" + expect_ids: [] + +# -- github:repo_archive -- +- test: "gh repo archive" + description: "match repo archive" + expect_ids: ["github:repo_archive"] +- test: "gh repo archive myorg/myrepo" + description: "match repo archive with org/repo" + expect_ids: ["github:repo_archive"] +- test: "gh repo archive --yes" + description: "match repo archive with --yes" + expect_ids: ["github:repo_archive"] +- test: "gh repo archive" + description: "match repo archive with extra spaces" + expect_ids: ["github:repo_archive"] +- test: "gh repo archived" + description: "negative: archived is not archive command" + expect_ids: [] +- test: "gh repo unarchive myorg/myrepo" + description: "negative: unarchive should not match archive" + expect_ids: [] + +# -- github:repo_rename -- +- test: "gh repo rename" + description: "match repo rename" + expect_ids: ["github:repo_rename"] +- test: "gh repo rename new-name" + description: "match repo rename with new name" + expect_ids: ["github:repo_rename"] +- test: "gh repo rename --yes new-name" + description: "match repo rename with --yes" + expect_ids: ["github:repo_rename"] +- test: "gh repo rename" + description: "match repo rename with extra spaces" + expect_ids: ["github:repo_rename"] +- test: "gh repo view" + description: "negative: repo view should not match" + expect_ids: [] + +# -- github:repo_change_visibility -- +- test: "gh repo edit --visibility private" + description: "match change visibility to private" + expect_ids: ["github:repo_change_visibility"] +- test: "gh repo edit --visibility public" + description: "match change visibility to public" + expect_ids: ["github:repo_change_visibility"] +- test: "gh repo edit --visibility internal" + description: "match change visibility to internal" + expect_ids: ["github:repo_change_visibility"] +- test: "gh repo edit myorg/myrepo --visibility private" + description: "match change visibility with org/repo" + expect_ids: ["github:repo_change_visibility"] +- test: "gh repo edit --description 'new desc' --visibility public" + description: "match change visibility with other flags before" + expect_ids: ["github:repo_change_visibility"] +- test: "gh repo edit --visibility private" + description: "match change visibility with extra spaces" + expect_ids: ["github:repo_change_visibility"] +- test: "gh repo edit --description 'new desc'" + description: "negative: repo edit without --visibility should not match" + expect_ids: [] +- test: "gh repo edit --homepage 'https://example.com'" + description: "negative: repo edit with other flags should not match" + expect_ids: [] + +# -- github:release_delete -- +- test: "gh release delete" + description: "match release delete" + expect_ids: ["github:release_delete"] +- test: "gh release delete v1.0.0" + description: "match release delete with tag" + expect_ids: ["github:release_delete"] +- test: "gh release delete v1.0.0 --yes" + description: "match release delete with --yes" + expect_ids: ["github:release_delete"] +- test: "gh release delete v1.0.0 --cleanup-tag" + description: "match release delete with --cleanup-tag" + expect_ids: ["github:release_delete"] +- test: "gh release delete" + description: "match release delete with extra spaces" + expect_ids: ["github:release_delete"] +- test: "gh release list" + description: "negative: release list should not match" + expect_ids: [] +- test: "gh release create v1.0.0" + description: "negative: release create should not match" + expect_ids: [] +- test: "gh release view v1.0.0" + description: "negative: release view should not match" + expect_ids: [] + +# -- github:secret_delete -- +- test: "gh secret delete" + description: "match secret delete" + expect_ids: ["github:secret_delete"] +- test: "gh secret delete MY_SECRET" + description: "match secret delete with secret name" + expect_ids: ["github:secret_delete"] +- test: "gh secret delete MY_SECRET --org myorg" + description: "match secret delete with --org flag" + expect_ids: ["github:secret_delete"] +- test: "gh secret delete MY_SECRET --env production" + description: "match secret delete with --env flag" + expect_ids: ["github:secret_delete"] +- test: "gh secret delete" + description: "match secret delete with extra spaces" + expect_ids: ["github:secret_delete"] +- test: "gh secret list" + description: "negative: secret list should not match" + expect_ids: [] +- test: "gh secret set MY_SECRET" + description: "negative: secret set should not match" + expect_ids: [] + +# -- github:variable_delete -- +- test: "gh variable delete" + description: "match variable delete" + expect_ids: ["github:variable_delete"] +- test: "gh variable delete MY_VAR" + description: "match variable delete with variable name" + expect_ids: ["github:variable_delete"] +- test: "gh variable delete MY_VAR --org myorg" + description: "match variable delete with --org flag" + expect_ids: ["github:variable_delete"] +- test: "gh variable delete MY_VAR --env staging" + description: "match variable delete with --env flag" + expect_ids: ["github:variable_delete"] +- test: "gh variable delete" + description: "match variable delete with extra spaces" + expect_ids: ["github:variable_delete"] +- test: "gh variable list" + description: "negative: variable list should not match" + expect_ids: [] +- test: "gh variable set MY_VAR" + description: "negative: variable set should not match" + expect_ids: [] diff --git a/shellfirm/tests/checks/heroku.yaml b/shellfirm/tests/checks/heroku.yaml index 7c50ef2..2e10e5e 100644 --- a/shellfirm/tests/checks/heroku.yaml +++ b/shellfirm/tests/checks/heroku.yaml @@ -207,3 +207,26 @@ - test: "herokuu clients:update" description: "invalid command" expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- heroku zero-space false positive tests -- +# All heroku rules use \s* which allows zero spaces between heroku and command +- test: "herokuapps:destroy" + description: "negative: no space between heroku and command should not match" + expect_ids: [] +- test: "herokucontainer:rm" + description: "negative: no space between heroku and command should not match" + expect_ids: [] +- test: "herokups:stop" + description: "negative: no space between heroku and command should not match" + expect_ids: [] +- test: "herokuconfig:unset" + description: "negative: no space between heroku and command should not match" + expect_ids: [] +- test: "herokumembers:remove" + description: "negative: no space between heroku and command should not match" + expect_ids: [] +- test: "herokurepo:reset" + description: "negative: no space between heroku and command should not match" + expect_ids: [] diff --git a/shellfirm/tests/checks/kubernetes.yaml b/shellfirm/tests/checks/kubernetes.yaml index 03359f9..be78c83 100644 --- a/shellfirm/tests/checks/kubernetes.yaml +++ b/shellfirm/tests/checks/kubernetes.yaml @@ -70,8 +70,8 @@ # -- kubernetes — additional coverage -- - test: "kubectl --context prod delete ns staging" - description: "BUG: --context between kubectl and delete" - expect_ids: [] + description: "match kubectl with --context flag before delete ns" + expect_ids: ["kubernetes-strict:delete_resource", "kubernetes:delete_namespace"] - test: "kubectl delete ns staging --dry-run=client" description: "dry-run=client contains --dry-run, filtered out" @@ -155,3 +155,81 @@ - test: "kubectl replace -f deployment.yaml --force --dry-run=client" description: "negative: replace --force with dry-run should not match" expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- kubernetes:delete_namespace edge cases -- +- test: "kubectl -n default delete ns staging" + description: "match kubectl with -n flag before delete ns" + expect_ids: ["kubernetes-strict:delete_resource", "kubernetes:delete_namespace"] + +# -- kubernetes:cordon_node edge cases -- +- test: "k uncordon node01" + description: "EDGE: k uncordon should not match cordon" + expect_ids: [] + +# -- kubernetes-strict:delete_resource edge cases -- +- test: "kubectl delete pod mypod --force --grace-period=0" + description: "EDGE: force delete pod" + expect_ids: ["kubernetes-strict:delete_resource"] +- test: "kubectl delete all --all -n default" + description: "EDGE: delete all resources" + expect_ids: ["kubernetes-strict:delete_resource", "kubernetes:delete_all_resources"] + +# -- kubernetes:delete_all_resources -- +- test: "kubectl delete pods --all" + description: "match delete all pods" + expect_ids: ["kubernetes-strict:delete_resource", "kubernetes:delete_all_resources"] +- test: "kubectl delete deployments --all -n production" + description: "match delete all deployments in namespace" + expect_ids: ["kubernetes-strict:delete_resource", "kubernetes:delete_all_resources"] +- test: "k delete services --all" + description: "match short alias delete all services" + expect_ids: ["kubernetes-strict:delete_resource", "kubernetes:delete_all_resources"] +- test: "kubectl -n staging delete pods --all" + description: "match delete all with -n flag before delete" + expect_ids: ["kubernetes-strict:delete_resource", "kubernetes:delete_all_resources"] +- test: "kubectl delete pods --all" + description: "match delete all with extra spaces" + expect_ids: ["kubernetes-strict:delete_resource", "kubernetes:delete_all_resources"] +- test: "kubectl delete pods --all --dry-run=client" + description: "negative: delete all with dry-run should not match" + expect_ids: [] +- test: "kubectl delete pod mypod" + description: "negative: delete specific pod should not match delete_all_resources" + expect_ids: ["kubernetes-strict:delete_resource"] +- test: "kubectl delete pods --field-selector status.phase=Failed" + description: "negative: delete with field-selector should not match delete_all" + expect_ids: ["kubernetes-strict:delete_resource"] +- test: "kubectl get pods --all-namespaces" + description: "negative: get with --all-namespaces should not match" + expect_ids: [] +- test: "kubectl delete pods --all-namespaces" + description: "negative: --all-namespaces is not --all (different flag)" + expect_ids: ["kubernetes-strict:delete_resource"] + +# -- kubernetes:apply_force -- +- test: "kubectl apply -f deployment.yaml --force" + description: "match apply with --force" + expect_ids: ["kubernetes:apply_force"] +- test: "kubectl apply --force -f deployment.yaml" + description: "match apply with --force before -f" + expect_ids: ["kubernetes:apply_force"] +- test: "k apply -f pod.yaml --force" + description: "match short alias apply --force" + expect_ids: ["kubernetes:apply_force"] +- test: "kubectl -n production apply -f deployment.yaml --force" + description: "match apply --force with -n flag before apply" + expect_ids: ["kubernetes:apply_force"] +- test: "kubectl apply -f deployment.yaml --force" + description: "match apply --force with extra spaces" + expect_ids: ["kubernetes:apply_force"] +- test: "kubectl apply -f deployment.yaml --force --dry-run=client" + description: "negative: apply --force with dry-run should not match" + expect_ids: [] +- test: "kubectl apply -f deployment.yaml" + description: "negative: apply without --force should not match" + expect_ids: [] +- test: "kubectl apply --force-conflicts -f deployment.yaml" + description: "negative: --force-conflicts is not --force (different flag)" + expect_ids: [] diff --git a/shellfirm/tests/checks/mongodb.yaml b/shellfirm/tests/checks/mongodb.yaml index 584633c..9ca6bc3 100644 --- a/shellfirm/tests/checks/mongodb.yaml +++ b/shellfirm/tests/checks/mongodb.yaml @@ -28,3 +28,21 @@ - test: "DB.DROPDATABASE()" description: "interactive MongoDB drop database uppercase" expect_ids: ["mongodb:interactive_drop_database"] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- mongodb:drop edge cases -- +- test: "mongo --eval db.users.drop()" + description: "match old mongo client with --eval drop" + expect_ids: ["mongodb:drop", "mongodb:interactive_drop_collection"] +- test: "mongosh 'mongodb://localhost/test' --eval 'db.dropDatabase()'" + description: "EDGE: connection string before --eval" + expect_ids: ["mongodb:drop", "mongodb:interactive_drop_database"] + +# -- mongodb:interactive_drop_collection edge cases -- +- test: "db.my_collection.drop()" + description: "EDGE: underscore in collection name" + expect_ids: ["mongodb:interactive_drop_collection"] +- test: "db.my-collection.drop()" + description: "match hyphenated collection name in drop" + expect_ids: ["mongodb:interactive_drop_collection"] diff --git a/shellfirm/tests/checks/mysql.yaml b/shellfirm/tests/checks/mysql.yaml index 27bc761..9c94fcd 100644 --- a/shellfirm/tests/checks/mysql.yaml +++ b/shellfirm/tests/checks/mysql.yaml @@ -18,3 +18,13 @@ - test: "mysql --execute 'DROP TABLE users'" description: "BUG: --execute long form not matched" expect_ids: ["database:drop_table"] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- mysql:drop edge cases -- +- test: "mysql -e 'SELECT * FROM dropdown'" + description: "negative: table named 'dropdown' should not match - DROP requires word boundary" + expect_ids: [] +- test: "mysql -h localhost -e 'DROP DATABASE test'" + description: "EDGE: -h flag before -e" + expect_ids: ["database:drop_database", "mysql:drop"] diff --git a/shellfirm/tests/checks/netlify.yaml b/shellfirm/tests/checks/netlify.yaml new file mode 100644 index 0000000..98a91a2 --- /dev/null +++ b/shellfirm/tests/checks/netlify.yaml @@ -0,0 +1,114 @@ +--- +# -- netlify:sites_delete -- +- test: "netlify sites:delete" + description: "match netlify sites:delete" + expect_ids: ["netlify:sites_delete"] +- test: "netlify sites:delete --force" + description: "match netlify sites:delete with --force" + expect_ids: ["netlify:sites_delete"] +- test: "netlify sites:delete --site-id abc123" + description: "match netlify sites:delete with --site-id" + expect_ids: ["netlify:sites_delete"] +- test: "netlify sites:delete" + description: "match netlify sites:delete with extra spaces" + expect_ids: ["netlify:sites_delete"] +- test: "netlify sites:deletee" + description: "negative: typo should not match" + expect_ids: [] +- test: "netlify sites:list" + description: "negative: sites:list should not match" + expect_ids: [] +- test: "netlify sites:create" + description: "negative: sites:create should not match" + expect_ids: [] +- test: "netlify deploy" + description: "negative: deploy should not match" + expect_ids: [] +# edge case: "netlify sites:delete" followed by more colon-syntax — ensure exact match +- test: "netlify sites:delete:extra" + description: "negative: sites:delete:extra should not match — 'delete' is followed by ':', not (\\s|$)" + expect_ids: [] +# edge case: "netlify sites:deleteall" — 'deleteall' is not 'delete' + (\s|$) +- test: "netlify sites:deleteall" + description: "negative: sites:deleteall should not match — not 'delete' followed by (\\s|$)" + expect_ids: [] +# edge case: sudo prefix +- test: "sudo netlify sites:delete" + description: "match netlify sites:delete with sudo prefix" + expect_ids: ["netlify:sites_delete"] +# edge case: "netlify sites:delete" at end of string with flag before +- test: "netlify sites:delete --force --site-id abc" + description: "match netlify sites:delete with multiple flags" + expect_ids: ["netlify:sites_delete"] +# edge case: "ntlify" typo — should not match +- test: "ntlify sites:delete" + description: "negative: ntlify (typo) should not match" + expect_ids: [] + +# -- netlify:env_unset -- +- test: "netlify env:unset MY_VAR" + description: "match netlify env:unset" + expect_ids: ["netlify:env_unset"] +- test: "netlify env:unset MY_VAR --context production" + description: "match netlify env:unset with --context" + expect_ids: ["netlify:env_unset"] +- test: "netlify env:unset MY_VAR" + description: "match netlify env:unset with extra spaces" + expect_ids: ["netlify:env_unset"] +- test: "netlify env:list" + description: "negative: env:list should not match" + expect_ids: [] +- test: "netlify env:set MY_VAR value" + description: "negative: env:set should not match" + expect_ids: [] +- test: "netlify env:get MY_VAR" + description: "negative: env:get should not match" + expect_ids: [] +# edge case: "netlify env:unset" with no variable — regex requires trailing \s+ so should NOT match +- test: "netlify env:unset" + description: "negative: netlify env:unset with no trailing content should not match (regex requires trailing \\s+)" + expect_ids: [] +# edge case: "netlify env:unsett MY_VAR" — typo, "env:unsett" won't match "env:unset\s+" +# regex: netlify\s+env:unset\s+. In "env:unsett MY_VAR", "env:unset" matches, then "t MY_VAR" — "t" is not \s. No match. +- test: "netlify env:unsett MY_VAR" + description: "negative: env:unsett typo should not match" + expect_ids: [] +# edge case: sudo prefix +- test: "sudo netlify env:unset MY_VAR" + description: "match netlify env:unset with sudo prefix" + expect_ids: ["netlify:env_unset"] +# edge case: "netlify env:unset MY_VAR --context production --scope builds" +- test: "netlify env:unset MY_VAR --context production --scope builds" + description: "match netlify env:unset with multiple flags" + expect_ids: ["netlify:env_unset"] + +# -- netlify:env_clone -- +- test: "netlify env:clone --to site-id" + description: "match netlify env:clone" + expect_ids: ["netlify:env_clone"] +- test: "netlify env:clone --from source-site --to target-site" + description: "match netlify env:clone with --from and --to" + expect_ids: ["netlify:env_clone"] +- test: "netlify env:clone --to site-id" + description: "match netlify env:clone with extra spaces" + expect_ids: ["netlify:env_clone"] +- test: "netlify env:list" + description: "negative: env:list should not match clone" + expect_ids: [] +# edge case: "netlify env:clone" with no arguments — regex requires trailing \s+ so no match +- test: "netlify env:clone" + description: "negative: netlify env:clone with no trailing content should not match (regex requires trailing \\s+)" + expect_ids: [] +# edge case: "netlify env:clonee --to site-id" — typo, "env:clonee" has extra e +# regex: netlify\s+env:clone\s+. "env:clone" matches, then "e --to" — "e" is not \s. No match. +- test: "netlify env:clonee --to site-id" + description: "negative: env:clonee typo should not match" + expect_ids: [] +# edge case: sudo prefix +- test: "sudo netlify env:clone --to site-id" + description: "match netlify env:clone with sudo prefix" + expect_ids: ["netlify:env_clone"] +# edge case: pipe before netlify +- test: "echo yes | netlify env:clone --to site-id" + description: "match netlify env:clone after pipe" + expect_ids: ["netlify:env_clone"] diff --git a/shellfirm/tests/checks/network.yaml b/shellfirm/tests/checks/network.yaml index 165989b..a8b9ca4 100644 --- a/shellfirm/tests/checks/network.yaml +++ b/shellfirm/tests/checks/network.yaml @@ -167,3 +167,60 @@ - test: "ufw reset --force" description: "BUG: --force after reset, not matched" expect_ids: [] + +# -- network:flush_nftables -- +- test: "nft flush ruleset" + description: "match nft flush ruleset" + expect_ids: ["network:flush_nftables"] +- test: "sudo nft flush ruleset" + description: "match nft flush ruleset with sudo" + expect_ids: ["network:flush_nftables"] +- test: "nft flush ruleset" + description: "match nft flush ruleset with extra spaces" + expect_ids: ["network:flush_nftables"] +- test: "nft list ruleset" + description: "negative: nft list should not match" + expect_ids: [] +- test: "nft flush chain filter input" + description: "negative: flushing specific chain should not match" + expect_ids: [] + +# -- network:flush_routes -- +- test: "ip route flush table main" + description: "match flush routing table" + expect_ids: ["network:flush_routes"] +- test: "sudo ip route flush table main" + description: "match flush routing table with sudo" + expect_ids: ["network:flush_routes"] +- test: "ip route flush table main" + description: "match flush routing table with extra spaces" + expect_ids: ["network:flush_routes"] +- test: "ip route flush cache" + description: "match flush route cache" + expect_ids: ["network:flush_routes"] +- test: "ip route show" + description: "negative: ip route show should not match" + expect_ids: [] +- test: "ip route add default via 192.168.1.1" + description: "negative: ip route add should not match" + expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- network:bring_down_interface_ip edge cases -- +- test: "ip link set br-abc123 down" + description: "match hyphenated interface name" + expect_ids: ["network:bring_down_interface_ip"] +- test: "ip link set docker0 down" + description: "EDGE: docker bridge interface" + expect_ids: ["network:bring_down_interface_ip"] + +# -- network:disable_firewall edge cases -- +- test: "ufw disabled" + description: "negative: 'ufw disabled' should not match - requires word boundary after disable" + expect_ids: [] + +# -- network:flush_iptables edge cases -- +- test: "ip6tables -F" + description: "match ip6tables flush rules" + expect_ids: ["network:flush_iptables"] diff --git a/shellfirm/tests/checks/npm.yaml b/shellfirm/tests/checks/npm.yaml new file mode 100644 index 0000000..89bc1d5 --- /dev/null +++ b/shellfirm/tests/checks/npm.yaml @@ -0,0 +1,142 @@ +--- +# -- npm:unpublish -- +- test: "npm unpublish" + description: "match npm unpublish" + expect_ids: ["npm:unpublish"] +- test: "npm unpublish my-package" + description: "match npm unpublish with package name" + expect_ids: ["npm:unpublish"] +- test: "npm unpublish my-package@1.0.0" + description: "match npm unpublish with package@version" + expect_ids: ["npm:unpublish"] +- test: "npm unpublish --force" + description: "match npm unpublish with --force" + expect_ids: ["npm:unpublish"] +- test: "npm unpublish my-package" + description: "match npm unpublish with extra spaces" + expect_ids: ["npm:unpublish"] +- test: "npm unpublishh" + description: "negative: typo should not match" + expect_ids: [] +- test: "npm publish" + description: "negative: publish should not match unpublish" + expect_ids: [] +- test: "npm install" + description: "negative: install should not match" + expect_ids: [] +- test: "npm list" + description: "negative: list should not match" + expect_ids: [] +# edge case: pnpm contains "npm" as substring — regex uses (^|\s)npm so "pnpm" should NOT match npm:unpublish +- test: "pnpm unpublish my-package" + description: "negative: pnpm should not match npm:unpublish (word boundary)" + expect_ids: ["npm:pnpm_unpublish"] +# edge case: sudo prefix — regex has (^|\s) so sudo+space+npm should match +- test: "sudo npm unpublish my-package" + description: "match npm unpublish with sudo prefix" + expect_ids: ["npm:unpublish"] +# edge case: pipe before npm — (^|\s) should match the space before npm +- test: "echo yes | npm unpublish my-package" + description: "match npm unpublish after pipe with spaces" + expect_ids: ["npm:unpublish"] +# edge case: npm at start of line, no trailing args (end of string boundary) +- test: "npm unpublish" + description: "match npm unpublish at end of string (boundary check)" + expect_ids: ["npm:unpublish"] +# edge case: tab character between npm and unpublish — \s should match tab +# UNCERTAIN: depends on whether test harness passes literal \t +# - test: "npm\tunpublish" +# description: "match npm unpublish with tab separator" +# expect_ids: ["npm:unpublish"] + +# -- npm:deprecate -- +- test: "npm deprecate my-package 'this package is deprecated'" + description: "match npm deprecate with message" + expect_ids: ["npm:deprecate"] +- test: "npm deprecate my-package@1.x 'use v2 instead'" + description: "match npm deprecate with version range" + expect_ids: ["npm:deprecate"] +- test: "npm deprecate my-package 'message'" + description: "match npm deprecate with extra spaces" + expect_ids: ["npm:deprecate"] +- test: "npm info my-package" + description: "negative: info should not match" + expect_ids: [] +# edge case: npm deprecate with NO trailing argument — regex requires trailing \s+ so bare "npm deprecate" with nothing after should NOT match +- test: "npm deprecate" + description: "negative: npm deprecate with no trailing content should not match (regex requires trailing \\s+)" + expect_ids: [] +# edge case: pnpm deprecate should not match npm:deprecate — (^|\s)npm won't match "pnpm" +- test: "pnpm deprecate my-package 'old'" + description: "negative: pnpm deprecate should not match npm:deprecate" + expect_ids: [] +# edge case: sudo npm deprecate +- test: "sudo npm deprecate my-package 'deprecated'" + description: "match npm deprecate with sudo prefix" + expect_ids: ["npm:deprecate"] + +# -- npm:yarn_unpublish -- +- test: "yarn npm unpublish" + description: "match yarn npm unpublish (also matches npm:unpublish as substring)" + expect_ids: ["npm:unpublish", "npm:yarn_unpublish"] +- test: "yarn npm unpublish my-package" + description: "match yarn npm unpublish with package" + expect_ids: ["npm:unpublish", "npm:yarn_unpublish"] +- test: "yarn npm unpublish my-package --force" + description: "match yarn npm unpublish with --force" + expect_ids: ["npm:unpublish", "npm:yarn_unpublish"] +- test: "yarn npm unpublish" + description: "match yarn npm unpublish with extra spaces" + expect_ids: ["npm:unpublish", "npm:yarn_unpublish"] +- test: "yarn npm publish" + description: "negative: yarn npm publish should not match" + expect_ids: [] +- test: "yarn install" + description: "negative: yarn install should not match" + expect_ids: [] +# edge case: yarn npm unpublishh — typo, "unpublishh" won't match (\s|$) +- test: "yarn npm unpublishh" + description: "negative: typo unpublishh should not match yarn_unpublish" + expect_ids: [] +# edge case: extra spaces between yarn npm unpublish — regex uses \s+ so should match +- test: "yarn npm unpublish my-package" + description: "match yarn npm unpublish with extra spaces and package" + expect_ids: ["npm:unpublish", "npm:yarn_unpublish"] +# edge case: "yarn npm" alone (no unpublish) +- test: "yarn npm info my-package" + description: "negative: yarn npm info should not match" + expect_ids: [] + +# -- npm:pnpm_unpublish -- +- test: "pnpm unpublish" + description: "match pnpm unpublish" + expect_ids: ["npm:pnpm_unpublish"] +- test: "pnpm unpublish my-package" + description: "match pnpm unpublish with package" + expect_ids: ["npm:pnpm_unpublish"] +- test: "pnpm unpublish my-package@1.0.0 --force" + description: "match pnpm unpublish with version and --force" + expect_ids: ["npm:pnpm_unpublish"] +- test: "pnpm unpublish" + description: "match pnpm unpublish with extra spaces" + expect_ids: ["npm:pnpm_unpublish"] +- test: "pnpm publish" + description: "negative: pnpm publish should not match" + expect_ids: [] +- test: "pnpm install" + description: "negative: pnpm install should not match" + expect_ids: [] +# edge case: pnpm unpublishh — typo should not match (\s|$) +- test: "pnpm unpublishh" + description: "negative: typo unpublishh should not match pnpm_unpublish" + expect_ids: [] +# edge case: sudo pnpm unpublish — regex is pnpm\s+unpublish, no (^|\s) prefix, so "sudo pnpm" should still match since "pnpm" is found mid-string +# UNCERTAIN: the regex `pnpm\s+unpublish(\s|$)` has no ^ or \s anchor at the start, so it will match anywhere in the string +- test: "sudo pnpm unpublish my-package" + description: "match pnpm unpublish with sudo prefix (no start anchor in regex)" + expect_ids: ["npm:pnpm_unpublish"] +# edge case: npnpm (extra char before pnpm) — no start-of-word boundary in regex, so "npnpm" WILL match "pnpm" substring +# UNCERTAIN: regex `pnpm\s+unpublish(\s|$)` has no word boundary, so "npnpm unpublish" likely matches +- test: "npnpm unpublish" + description: "negative: npnpm should not match pnpm_unpublish (word boundary via (^|\\s))" + expect_ids: [] diff --git a/shellfirm/tests/checks/psql.yaml b/shellfirm/tests/checks/psql.yaml index c7375da..a998bf2 100644 --- a/shellfirm/tests/checks/psql.yaml +++ b/shellfirm/tests/checks/psql.yaml @@ -18,3 +18,13 @@ - test: "psql --command 'DROP DATABASE test'" description: "BUG: --command long form not matched" expect_ids: ["database:drop_database"] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- psql:drop edge cases -- +- test: "psql -c 'SELECT * FROM dropdown'" + description: "negative: table named 'dropdown' should not match - DROP requires word boundary" + expect_ids: [] +- test: "psql -h localhost -c 'DROP TABLE users'" + description: "EDGE: -h flag before -c" + expect_ids: ["database:drop_table", "psql:drop"] diff --git a/shellfirm/tests/checks/redis.yaml b/shellfirm/tests/checks/redis.yaml index 141933b..b73e0bd 100644 --- a/shellfirm/tests/checks/redis.yaml +++ b/shellfirm/tests/checks/redis.yaml @@ -56,3 +56,24 @@ - test: " shutdown " description: "interactive shutdown with whitespace and lowercase" expect_ids: ["base:shutdown_machine", "redis:interactive_shutdown"] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- redis:flushall edge cases -- +- test: "redis-cli -h 10.0.0.1 FLUSHALL" + description: "match redis-cli with -h flag before FLUSHALL" + expect_ids: ["redis:flushall"] +- test: "redis-cli -n 0 FLUSHALL" + description: "match redis-cli with -n flag before FLUSHALL" + expect_ids: ["redis:flushall"] +- test: "redis-cli -p 6380 FLUSHALL" + description: "match redis-cli with -p flag before FLUSHALL" + expect_ids: ["redis:flushall"] + +# -- redis:flushdb edge cases -- +- test: "redis-cli -h localhost FLUSHDB" + description: "match redis-cli with -h flag before FLUSHDB" + expect_ids: ["redis:flushdb"] +- test: "redis-cli -p 6380 FLUSHDB" + description: "match redis-cli with -p flag before FLUSHDB" + expect_ids: ["redis:flushdb"] diff --git a/shellfirm/tests/checks/shell.yaml b/shellfirm/tests/checks/shell.yaml index f960edc..ce8c7ee 100644 --- a/shellfirm/tests/checks/shell.yaml +++ b/shellfirm/tests/checks/shell.yaml @@ -43,3 +43,62 @@ - test: "curl https://example.com/install.sh" description: "negative: curl without eval should not match eval check" expect_ids: [] + +# -- shell:curl_pipe_to_shell — extra spaces -- +- test: "curl https://example.com/install.sh | bash" + description: "match curl pipe to bash with many spaces" + expect_ids: ["shell:curl_pipe_to_shell"] +- test: "curl https://example.com/install.sh | sh" + description: "match curl pipe to sh with extra spaces around pipe" + expect_ids: ["shell:curl_pipe_to_shell"] +- test: "curl -fsSL https://example.com/setup | zsh" + description: "match curl pipe to zsh with extra spaces everywhere" + expect_ids: ["shell:curl_pipe_to_shell"] + +# -- shell:curl_pipe_to_interpreter -- +- test: "curl https://example.com/install.py | python" + description: "match curl piped to python" + expect_ids: ["shell:curl_pipe_to_interpreter"] +- test: "curl https://example.com/install.py | python3" + description: "match curl piped to python3" + expect_ids: ["shell:curl_pipe_to_interpreter"] +- test: "curl -fsSL https://example.com/script.pl | perl" + description: "match curl piped to perl" + expect_ids: ["shell:curl_pipe_to_interpreter"] +- test: "curl https://example.com/script.rb | ruby" + description: "match curl piped to ruby" + expect_ids: ["shell:curl_pipe_to_interpreter"] +- test: "curl https://example.com/install.py | python3" + description: "match curl piped to python3 with many spaces" + expect_ids: ["shell:curl_pipe_to_interpreter"] +- test: "curl -o script.py https://example.com/install.py" + description: "negative: curl saving to file should not match interpreter check" + expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- shell:curl_pipe_to_shell edge cases -- +- test: "curl -sS https://example.com/setup | sudo bash" + description: "match curl piped to sudo bash" + expect_ids: ["shell:curl_pipe_to_shell"] +- test: "curl -L https://example.com/setup | bash -x" + description: "EDGE: bash with flags after pipe" + expect_ids: ["shell:curl_pipe_to_shell"] +- test: "curl https://example.com | dash" + description: "EDGE: dash shell not in pattern list" + expect_ids: [] + +# -- shell:wget_pipe_to_shell edge cases -- +- test: "wget -qO - https://example.com | bash" + description: "match wget with combined -qO flag piped to bash" + expect_ids: ["shell:wget_pipe_to_shell"] + +# -- shell:eval_curl edge cases -- +- test: "eval $(curl https://example.com)" + description: "match eval with unquoted curl subshell" + expect_ids: ["shell:eval_curl"] + +# -- shell:curl_pipe_to_interpreter edge cases -- +- test: "curl https://example.com | pythonista" + description: "EDGE: word starting with python should not match (word boundary)" + expect_ids: [] diff --git a/shellfirm/tests/checks/terraform.yaml b/shellfirm/tests/checks/terraform.yaml index e107de6..58beb28 100644 --- a/shellfirm/tests/checks/terraform.yaml +++ b/shellfirm/tests/checks/terraform.yaml @@ -62,3 +62,57 @@ - test: "terraform workspace delete" description: "invalid command" expect_ids: [] + +# -- terraform:destroy_auto_approve -- +- test: "terraform destroy -auto-approve" + description: "match destroy with auto-approve" + expect_ids: ["terraform:destroy_auto_approve"] +- test: "terraform destroy -auto-approve" + description: "match destroy with extra spaces" + expect_ids: ["terraform:destroy_auto_approve"] +- test: "terraform destroy -target=aws_instance.foo -auto-approve" + description: "match destroy with target and auto-approve" + expect_ids: ["terraform:destroy_auto_approve"] +- test: "terraform destroy" + description: "negative: destroy without auto-approve should not match" + expect_ids: [] +- test: "terraform destroy -target=aws_instance.foo" + description: "negative: destroy with target but no auto-approve should not match" + expect_ids: [] + +# -- terraform:state_rm -- +- test: "terraform state rm aws_instance.foo" + description: "match state rm" + expect_ids: ["terraform:state_rm"] +- test: "terraform state rm aws_instance.foo" + description: "match state rm with extra spaces" + expect_ids: ["terraform:state_rm"] +- test: "terraform state rm module.foo" + description: "match state rm with module" + expect_ids: ["terraform:state_rm"] +- test: "terraform state list" + description: "negative: state list should not match" + expect_ids: [] +- test: "terraform state show aws_instance.foo" + description: "negative: state show should not match" + expect_ids: [] + +# ====== EDGE CASE / STRESS TESTS ====== + +# -- terraform:apply_with_auto_approve edge cases -- +- test: "terraform apply -var='foo=bar' -auto-approve" + description: "EDGE: -var before -auto-approve" + expect_ids: ["terraform:apply_with_auto_approve"] +- test: "terraform apply plan.tfplan" + description: "EDGE: applying from saved plan should NOT match" + expect_ids: [] + +# -- terraform:destroy_auto_approve edge cases -- +- test: "terraform destroy -target=aws_instance.foo -auto-approve" + description: "EDGE: target before auto-approve" + expect_ids: ["terraform:destroy_auto_approve"] + +# -- terraform:force_unlock edge cases -- +- test: "terraform force-unlock LOCK_ID -force" + description: "match force-unlock with -force after lock ID" + expect_ids: ["terraform:force_unlock_with_force_flag"] diff --git a/shellfirm/tests/checks/vercel.yaml b/shellfirm/tests/checks/vercel.yaml new file mode 100644 index 0000000..c38706b --- /dev/null +++ b/shellfirm/tests/checks/vercel.yaml @@ -0,0 +1,175 @@ +--- +# -- vercel:remove -- +- test: "vercel remove" + description: "match vercel remove" + expect_ids: ["vercel:remove"] +- test: "vercel remove my-project" + description: "match vercel remove with project name" + expect_ids: ["vercel:remove"] +- test: "vercel rm" + description: "match vercel rm (short alias)" + expect_ids: ["vercel:remove"] +- test: "vercel rm my-project --yes" + description: "match vercel rm with --yes" + expect_ids: ["vercel:remove"] +- test: "vercel remove my-project" + description: "match vercel remove with extra spaces" + expect_ids: ["vercel:remove"] +- test: "vercel removee" + description: "negative: typo should not match" + expect_ids: [] +- test: "vercel list" + description: "negative: list should not match" + expect_ids: [] +- test: "vercel deploy" + description: "negative: deploy should not match" + expect_ids: [] +# edge case: "vercel rmm" — typo, (remove|rm) won't match "rmm", but (\s|$) check on "rmm" +# regex is vercel\s+(remove|rm)(\s|$). "rmm" would need to match (remove|rm)(\s|$). "rm" matches (rm), then "m" must match (\s|$) — it doesn't. So no match. +- test: "vercel rmm" + description: "negative: rmm typo should not match" + expect_ids: [] +# edge case: "vercel rm" at end of string — (\s|$) with $ should match +- test: "vercel rm" + description: "match vercel rm at end of string" + expect_ids: ["vercel:remove"] +# edge case: sudo prefix +- test: "sudo vercel remove my-project" + description: "match vercel remove with sudo prefix" + expect_ids: ["vercel:remove"] +# edge case: "vercel removal" — "removal" is not "remove" per (remove|rm) alternation +# regex: vercel\s+(remove|rm)(\s|$). "removal" — (remove) matches "remove", then "al" must match (\s|$) — fails. No match. +- test: "vercel removal" + description: "negative: removal should not match (not exact remove)" + expect_ids: [] +# edge case: vercel rm with flags before project +- test: "vercel rm --yes my-project" + description: "match vercel rm with flags before project name" + expect_ids: ["vercel:remove"] + +# -- vercel:project_remove -- +- test: "vercel project remove" + description: "match vercel project remove" + expect_ids: ["vercel:project_remove"] +- test: "vercel project remove my-project" + description: "match vercel project remove with name" + expect_ids: ["vercel:project_remove"] +- test: "vercel project rm" + description: "match vercel project rm (short alias)" + expect_ids: ["vercel:project_remove"] +- test: "vercel project rm my-project" + description: "match vercel project rm with name" + expect_ids: ["vercel:project_remove"] +- test: "vercel project remove" + description: "match vercel project remove with extra spaces" + expect_ids: ["vercel:project_remove"] +- test: "vercel project list" + description: "negative: project list should not match" + expect_ids: [] +# edge case: "vercel project rmm" — typo should not match +- test: "vercel project rmm" + description: "negative: project rmm typo should not match" + expect_ids: [] +# edge case: "vercel project removee" — typo should not match +- test: "vercel project removee" + description: "negative: project removee typo should not match" + expect_ids: [] +# edge case: sudo vercel project rm +- test: "sudo vercel project rm my-project" + description: "match vercel project rm with sudo prefix" + expect_ids: ["vercel:project_remove"] +# edge case: "vercel project remove" at end of string +- test: "vercel project rm" + description: "match vercel project rm at end of string" + expect_ids: ["vercel:project_remove"] + +# -- vercel:env_remove -- +- test: "vercel env rm MY_VAR" + description: "match vercel env rm" + expect_ids: ["vercel:env_remove"] +- test: "vercel env remove MY_VAR" + description: "match vercel env remove" + expect_ids: ["vercel:env_remove"] +- test: "vercel env rm MY_VAR production" + description: "match vercel env rm with environment" + expect_ids: ["vercel:env_remove"] +- test: "vercel env rm MY_VAR" + description: "match vercel env rm with extra spaces" + expect_ids: ["vercel:env_remove"] +- test: "vercel env ls" + description: "negative: env ls should not match" + expect_ids: [] +- test: "vercel env add MY_VAR" + description: "negative: env add should not match" + expect_ids: [] +- test: "vercel env pull" + description: "negative: env pull should not match" + expect_ids: [] +# edge case: "vercel env rm" with no trailing var — regex requires trailing \s+ so no match +- test: "vercel env rm" + description: "negative: vercel env rm with no trailing content should not match (regex requires trailing \\s+)" + expect_ids: [] +# edge case: "vercel env remove" with no trailing var — same reason +- test: "vercel env remove" + description: "negative: vercel env remove with no trailing content should not match (regex requires trailing \\s+)" + expect_ids: [] +# edge case: sudo vercel env rm +- test: "sudo vercel env rm MY_VAR" + description: "match vercel env rm with sudo prefix" + expect_ids: ["vercel:env_remove"] +# edge case: "vercel env rmm MY_VAR" — typo, (rm|remove) won't match "rmm" properly +# regex: vercel\s+env\s+(rm|remove)\s+. "rmm" — "rm" matches (rm), then "m MY_VAR" must match \s+. "m" is not whitespace. No match. +- test: "vercel env rmm MY_VAR" + description: "negative: env rmm typo should not match" + expect_ids: [] + +# -- vercel:domain_remove -- +- test: "vercel domains rm" + description: "match vercel domains rm" + expect_ids: ["vercel:domain_remove"] +- test: "vercel domains rm example.com" + description: "match vercel domains rm with domain" + expect_ids: ["vercel:domain_remove"] +- test: "vercel domain rm example.com" + description: "match vercel domain (singular) rm" + expect_ids: ["vercel:domain_remove"] +- test: "vercel domains remove" + description: "match vercel domains remove" + expect_ids: ["vercel:domain_remove"] +- test: "vercel domain remove example.com" + description: "match vercel domain remove with domain" + expect_ids: ["vercel:domain_remove"] +- test: "vercel domains rm example.com" + description: "match vercel domains rm with extra spaces" + expect_ids: ["vercel:domain_remove"] +- test: "vercel domains ls" + description: "negative: domains ls should not match" + expect_ids: [] +- test: "vercel domains add example.com" + description: "negative: domains add should not match" + expect_ids: [] +# edge case: "vercel domains rmm" — typo should not match +# regex: vercel\s+domains?\s+(rm|remove)(\s|$). "rmm" — "rm" matches, then "m" must match (\s|$) — fails. +- test: "vercel domains rmm" + description: "negative: domains rmm typo should not match" + expect_ids: [] +# edge case: "vercel domainss rm" — typo extra s. domains? matches "domain"+"s" (optional s), leaving "s rm". That "s" won't match \s+. No match. +- test: "vercel domainss rm" + description: "negative: domainss typo should not match" + expect_ids: [] +# edge case: "vercel domains rm" at end of string — (\s|$) with $ should match +- test: "vercel domains rm" + description: "match vercel domains rm at end of string" + expect_ids: ["vercel:domain_remove"] +# edge case: "vercel domains remove" at end of string +- test: "vercel domains remove" + description: "match vercel domains remove at end of string" + expect_ids: ["vercel:domain_remove"] +# edge case: sudo prefix +- test: "sudo vercel domain rm example.com" + description: "match vercel domain rm with sudo prefix" + expect_ids: ["vercel:domain_remove"] +# edge case: "vercel domain remove" at end of string (singular) +- test: "vercel domain remove" + description: "match vercel domain (singular) remove at end of string" + expect_ids: ["vercel:domain_remove"] diff --git a/shellfirm/tests/decision_matrix.rs b/shellfirm/tests/decision_matrix.rs index 6b93090..8b507a0 100644 --- a/shellfirm/tests/decision_matrix.rs +++ b/shellfirm/tests/decision_matrix.rs @@ -136,7 +136,9 @@ fn default_settings() -> Settings { enabled_groups: vec![ "base".into(), "fs".into(), + "flyio".into(), "git".into(), + "github".into(), "kubernetes".into(), "docker".into(), "aws".into(), @@ -145,7 +147,10 @@ fn default_settings() -> Settings { "database".into(), "terraform".into(), "heroku".into(), + "netlify".into(), "network".into(), + "npm".into(), + "vercel".into(), ], audit_enabled: false, ..Settings::default()