use crate::verdict::{SafetyLevel, Verdict};
use crate::parse::{Token, WordSet};
use crate::policy::{self, FlagPolicy, FlagStyle};
static BUNDLE_CONFIG_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&["--help", "-h"]),
valued: WordSet::flags(&[]),
bare: true,
max_positional: Some(1),
flag_style: FlagStyle::Strict,
};
static BUNDLE_CONFIG_GET_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&["--help", "-h"]),
valued: WordSet::flags(&[]),
bare: false,
max_positional: None,
flag_style: FlagStyle::Strict,
};
static HELP_ONLY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&["--help", "-h"]),
valued: WordSet::flags(&[]),
bare: false,
max_positional: Some(0),
flag_style: FlagStyle::Strict,
};
static BUNDLE_EXEC_SAFE: WordSet = WordSet::new(&[
"brakeman", "cucumber", "erb_lint", "herb", "rspec", "standardrb",
]);
static RAILS_ROUTES_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&["--expanded", "--help", "-h"]),
valued: WordSet::flags(&["--controller", "--grep", "-g"]),
bare: true,
max_positional: None,
flag_style: FlagStyle::Strict,
};
static RAILS_TEST_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&[
"--backtrace", "--color", "--defer-output", "--fail-fast",
"--help", "--no-color", "--verbose",
"-b", "-c", "-d", "-f", "-h", "-v",
]),
valued: WordSet::flags(&["--environment", "--name", "--seed", "-e", "-n", "-s"]),
bare: true,
max_positional: None,
flag_style: FlagStyle::Strict,
};
static RAILS_NOTES_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&["--help", "-h"]),
valued: WordSet::flags(&["--annotations", "-a"]),
bare: true,
max_positional: None,
flag_style: FlagStyle::Strict,
};
static RAILS_BARE_SUBS: WordSet = WordSet::new(&[
"about", "assets:reveal", "assets:reveal:full",
"db:migrate:status", "db:version",
"initializers", "middleware", "secret", "stats",
"time:zones:all", "time:zones:local", "version",
]);
pub fn check_bundle_config(tokens: &[Token]) -> Verdict {
if tokens.len() == 1 {
return Verdict::Allowed(SafetyLevel::Inert);
}
let sub = tokens[1].as_str();
if tokens.len() == 2 && (sub == "--help" || sub == "-h") {
return Verdict::Allowed(SafetyLevel::Inert);
}
match sub {
"get" | "list" => {
if policy::check(&tokens[1..], &BUNDLE_CONFIG_GET_POLICY) {
return Verdict::Allowed(SafetyLevel::Inert);
}
}
"set" | "unset" => {
if policy::check(&tokens[1..], &HELP_ONLY) {
return Verdict::Allowed(SafetyLevel::Inert);
}
return Verdict::Denied;
}
_ => {}
}
if policy::check(tokens, &BUNDLE_CONFIG_POLICY) { Verdict::Allowed(SafetyLevel::Inert) } else { Verdict::Denied }
}
pub fn check_bundle_exec(tokens: &[Token]) -> Verdict {
let Some(cmd) = tokens.get(1) else {
return Verdict::Denied;
};
if tokens.len() == 2 && (cmd == "--help" || cmd == "-h") {
return Verdict::Allowed(SafetyLevel::Inert);
}
if BUNDLE_EXEC_SAFE.contains(cmd) {
return Verdict::Allowed(SafetyLevel::SafeRead);
}
if cmd == "rails" {
return check_rails_sub(&tokens[1..]);
}
if cmd == "gem" {
let inner = shell_words::join(tokens[1..].iter().map(|t| t.as_str()));
return crate::command_verdict(&inner);
}
if cmd == "appraisal" {
return check_appraisal(&tokens[1..]);
}
Verdict::Denied
}
fn check_appraisal(tokens: &[Token]) -> Verdict {
if tokens.len() < 2 {
return Verdict::Denied;
}
let sub = &tokens[1];
if tokens.len() == 2 && (sub == "--help" || sub == "-h") {
return Verdict::Allowed(SafetyLevel::Inert);
}
if sub == "list" {
return if tokens.len() == 2 { Verdict::Allowed(SafetyLevel::Inert) } else { Verdict::Denied };
}
if tokens.len() < 3 {
return Verdict::Denied;
}
check_bundle_exec(&tokens[1..])
}
fn check_rails_sub(tokens: &[Token]) -> Verdict {
if tokens.len() < 2 {
return Verdict::Denied;
}
let sub = &tokens[1];
if tokens.len() == 2 && (sub == "--help" || sub == "-h") {
return Verdict::Allowed(SafetyLevel::Inert);
}
match sub.as_str() {
"routes" => if policy::check(&tokens[1..], &RAILS_ROUTES_POLICY) { Verdict::Allowed(SafetyLevel::Inert) } else { Verdict::Denied },
"test" | "test:system" => if policy::check(&tokens[1..], &RAILS_TEST_POLICY) { Verdict::Allowed(SafetyLevel::SafeRead) } else { Verdict::Denied },
"notes" => if policy::check(&tokens[1..], &RAILS_NOTES_POLICY) { Verdict::Allowed(SafetyLevel::Inert) } else { Verdict::Denied },
_ => if RAILS_BARE_SUBS.contains(sub) && tokens.len() == 2 { Verdict::Allowed(SafetyLevel::Inert) } else { Verdict::Denied },
}
}
#[cfg(test)]
mod tests {
use crate::is_safe_command;
fn check(cmd: &str) -> bool {
is_safe_command(cmd)
}
safe! {
bundle_list: "bundle list",
bundle_list_name_only: "bundle list --name-only",
bundle_list_paths: "bundle list --paths",
bundle_info: "bundle info rails",
bundle_info_path: "bundle info rails --path",
bundle_show: "bundle show actionpack",
bundle_show_paths: "bundle show --paths",
bundle_check: "bundle check",
bundle_check_dry_run: "bundle check --dry-run",
bundle_exec_rspec: "bundle exec rspec spec/models/foo_spec.rb",
bundle_exec_standardrb: "bundle exec standardrb app/models/foo.rb",
bundle_exec_standardrb_fix: "bundle exec standardrb --fix app/models/foo.rb",
bundle_exec_cucumber: "bundle exec cucumber",
bundle_exec_brakeman: "bundle exec brakeman",
bundle_exec_erb_lint: "bundle exec erb_lint app/views/foo.html.erb",
bundle_exec_herb: "bundle exec herb app/views/foo.html.erb",
bundle_exec_rails_routes: "bundle exec rails routes",
bundle_exec_rails_routes_grep: "bundle exec rails routes --grep dun_and_bradstreet",
bundle_exec_rails_routes_controller: "bundle exec rails routes --controller users",
bundle_exec_rails_routes_expanded: "bundle exec rails routes --expanded",
bundle_exec_rails_about: "bundle exec rails about",
bundle_exec_rails_stats: "bundle exec rails stats",
bundle_exec_rails_notes: "bundle exec rails notes",
bundle_exec_rails_notes_annotations: "bundle exec rails notes --annotations FIXME RELEASE",
bundle_exec_rails_version: "bundle exec rails version",
bundle_exec_rails_help: "bundle exec rails --help",
bundle_exec_rails_initializers: "bundle exec rails initializers",
bundle_exec_rails_middleware: "bundle exec rails middleware",
bundle_exec_rails_secret: "bundle exec rails secret",
bundle_exec_rails_db_migrate_status: "bundle exec rails db:migrate:status",
bundle_exec_rails_db_version: "bundle exec rails db:version",
bundle_exec_rails_time_zones_all: "bundle exec rails time:zones:all",
bundle_exec_rails_time_zones_local: "bundle exec rails time:zones:local",
bundle_exec_rails_assets_reveal: "bundle exec rails assets:reveal",
bundle_exec_rails_assets_reveal_full: "bundle exec rails assets:reveal:full",
bundle_exec_rails_test: "bundle exec rails test",
bundle_exec_rails_test_file: "bundle exec rails test test/models/user_test.rb",
bundle_exec_rails_test_seed: "bundle exec rails test --seed 1234",
bundle_exec_rails_test_verbose: "bundle exec rails test -v",
bundle_exec_rails_test_fail_fast: "bundle exec rails test --fail-fast",
bundle_exec_rails_test_name: "bundle exec rails test -n /user/",
bundle_exec_rails_test_env: "bundle exec rails test -e test",
bundle_exec_rails_test_system: "bundle exec rails test:system",
bundle_config_bare: "bundle config",
bundle_config_key: "bundle config path",
bundle_config_list: "bundle config list",
bundle_config_get: "bundle config get path",
bundle_config_help: "bundle config --help",
bundle_config_set_help: "bundle config set --help",
bundle_version: "bundle --version",
bundle_exec_gem_list: "bundle exec gem list",
bundle_exec_gem_list_local: "bundle exec gem list --local",
bundle_exec_gem_dependency: "bundle exec gem dependency rails",
bundle_exec_gem_info: "bundle exec gem info rails",
bundle_exec_gem_which: "bundle exec gem which bundler",
bundle_exec_gem_environment: "bundle exec gem environment",
bundle_exec_gem_contents: "bundle exec gem contents rails",
bundle_exec_gem_search: "bundle exec gem search rails",
bundle_exec_gem_outdated: "bundle exec gem outdated",
bundle_exec_gem_help: "bundle exec gem --help",
bundle_exec_gem_version: "bundle exec gem --version",
bundle_exec_appraisal_list: "bundle exec appraisal list",
bundle_exec_appraisal_help: "bundle exec appraisal --help",
bundle_exec_appraisal_rspec: "bundle exec appraisal rails-7-1 rspec spec/models/foo_spec.rb",
bundle_exec_appraisal_rspec_tag: "bundle exec appraisal rails-7-1 rspec --tag focus spec/",
bundle_exec_appraisal_cucumber: "bundle exec appraisal rails-7-1 cucumber",
}
denied! {
bundle_exec_rails_console_denied: "bundle exec rails console",
bundle_exec_rails_server_denied: "bundle exec rails server",
bundle_exec_rails_generate_denied: "bundle exec rails generate model User",
bundle_exec_rails_destroy_denied: "bundle exec rails destroy model User",
bundle_exec_rails_db_migrate_denied: "bundle exec rails db:migrate",
bundle_exec_rails_db_drop_denied: "bundle exec rails db:drop",
bundle_exec_rails_db_seed_denied: "bundle exec rails db:seed",
bundle_exec_rails_db_reset_denied: "bundle exec rails db:reset",
bundle_exec_rails_db_setup_denied: "bundle exec rails db:setup",
bundle_exec_rails_db_rollback_denied: "bundle exec rails db:rollback",
bundle_exec_rails_db_create_denied: "bundle exec rails db:create",
bundle_exec_rails_db_schema_load_denied: "bundle exec rails db:schema:load",
bundle_exec_rails_runner_denied: "bundle exec rails runner script.rb",
bundle_exec_rails_dbconsole_denied: "bundle exec rails dbconsole",
bundle_exec_rails_credentials_edit_denied: "bundle exec rails credentials:edit",
bundle_exec_rails_bare_denied: "bundle exec rails",
bundle_exec_rake_denied: "bundle exec rake db:drop",
bundle_exec_ruby_denied: "bundle exec ruby script.rb",
bundle_config_set_denied: "bundle config set path vendor/bundle",
bundle_config_unset_denied: "bundle config unset path",
bundle_config_old_set_denied: "bundle config path vendor/bundle",
bundle_exec_gem_install_denied: "bundle exec gem install rails",
bundle_exec_gem_uninstall_denied: "bundle exec gem uninstall rails",
bundle_exec_gem_update_denied: "bundle exec gem update",
bundle_exec_gem_bare_denied: "bundle exec gem",
bundle_exec_appraisal_bare_denied: "bundle exec appraisal",
bundle_exec_appraisal_gemfile_only_denied: "bundle exec appraisal rails-7-1",
bundle_exec_appraisal_rm_denied: "bundle exec appraisal rails-7-1 rm -rf /",
bundle_exec_appraisal_list_extra_denied: "bundle exec appraisal list foo",
bundle_exec_appraisal_list_flag_denied: "bundle exec appraisal list --unknown",
}
}