use candor_report::EFFECTS;
pub mod policy;
pub fn classify_extra(
crate_name: &str,
path: &str,
extra: &[(&'static str, bool, String)],
) -> Option<&'static str> {
for (eff, is_crate, prefix) in extra {
let hit = if *is_crate { crate_name.starts_with(prefix.as_str()) } else { path.starts_with(prefix.as_str()) };
if hit {
return Some(eff);
}
}
None
}
pub const CALIBRATED_CRATES: [&str; 59] = [
"reqwest", "isahc", "ureq", "curl", "aws_config", "git2", "tokio_tcp", "tokio_udp", "async_net",
"async_nats", "lapin", "lettre", "tungstenite", "elasticsearch", "tonic", "rdkafka", "pnet",
"ignore", "notify",
"sqlx", "rusqlite", "postgres", "tokio_postgres", "diesel", "redis", "mongodb",
"mysql", "mysql_async", "sea_orm", "deadpool_postgres",
"memmap2", "fs_err", "async_fs", "tempfile", "glob",
"rand", "getrandom", "fastrand",
"argon2", "bcrypt", "scrypt", "pbkdf2", "password_hash", "rand_core",
"portable_pty", "async_process", "duct",
"dotenvy", "dotenv",
"chrono", "time", "tracing", "log", "arboard",
"rustc_lint", "rustc_errors",
"libc", "nix", "rustix",
];
pub const CALIBRATED_PREFIXES: [&str; 3] = ["aws_sdk_", "aws_smithy", "cap_"];
pub const PATH_CALIBRATED_CRATES: [&str; 3] = ["tokio", "async_std", "mio"];
pub const CALIBRATION_PROBE_TAILS: &[&str] = &[
"::X::send", "::X::execute", "::X::call", "::X::query", "::X::fetch_one", "::Remote::fetch",
"::datalink::channel", "::WalkBuilder::build_parallel", "::RecommendedWatcher::new",
"::X::connect", "::Utc::now", "::X::load", "::__private_api::log", "::tempfile", "::glob",
"::X::run", "::dotenv", "::random", "::emit", "::X::emit_span_lint", "::X::anything",
"::SaltString::generate", "::hash", "::OsRng::fill_bytes",
"::Mmap::map", "::event", "::u32", "::Clipboard::get_text", "::spawn_command",
];
pub const DB_CRATES: [&str; 11] = [
"sqlx", "rusqlite", "postgres", "tokio_postgres", "diesel", "redis", "mongodb",
"mysql", "mysql_async", "sea_orm", "deadpool_postgres",
];
const PURE_FD_TRANSFER: &[&str] = &[
"from_raw_fd", "from_raw_socket", "from_raw_handle",
"into_raw_fd", "into_raw_socket", "into_raw_handle",
"as_raw_fd", "as_raw_socket", "as_raw_handle",
"into_std",
"from_pathname",
];
pub fn classify(crate_name: &str, path: &str) -> Option<&'static str> {
if PURE_FD_TRANSFER.contains(&path.rsplit("::").next().unwrap_or(path)) {
return None;
}
if crate_name.starts_with("aws_sdk_") || crate_name.starts_with("aws_smithy") {
if path.ends_with("::send") || path.ends_with("::send_with") {
return Some("Net");
}
return None;
}
if crate_name == "aws_config" {
if path.ends_with("::load") || path.ends_with("::load_defaults") {
return Some("Net");
}
return None;
}
if crate_name == "git2" {
if path.ends_with("::fetch")
|| path.ends_with("::push")
|| path.ends_with("::download")
|| path.ends_with("::connect")
|| path.ends_with("::connect_auth")
|| path.ends_with("::ls")
|| path.ends_with("::upload")
{
return Some("Net");
}
return None;
}
if crate_name == "libc" || crate_name == "nix" || crate_name == "rustix" {
let f = path.rsplit("::").next().unwrap_or(path);
const FS: &[&str] = &[
"open", "open64", "openat", "openat2", "creat", "creat64", "stat", "stat64", "lstat",
"lstat64", "fstatat", "fstatat64", "newfstatat", "statx", "access", "faccessat",
"faccessat2", "mkdir", "mkdirat", "rmdir", "unlink", "unlinkat", "rename", "renameat",
"renameat2", "link", "linkat", "symlink", "symlinkat", "readlink", "readlinkat", "chmod",
"fchmodat", "chown", "lchown", "fchownat", "truncate", "truncate64", "ftruncate",
"ftruncate64", "opendir", "fdopendir", "readdir", "readdir64", "readdir_r", "closedir",
"rewinddir", "seekdir", "telldir", "scandir", "mkstemp", "mkstemps", "mkostemp", "mkdtemp",
"mknod", "mknodat", "chdir", "fchdir", "getcwd", "get_current_dir_name", "chroot",
"pivot_root", "statfs", "statfs64", "fstatfs", "fstatfs64", "statvfs", "fstatvfs", "mount",
"umount", "umount2", "fsync", "fdatasync", "sync", "syncfs", "sync_file_range", "fallocate",
"posix_fallocate", "posix_fadvise", "sendfile", "sendfile64", "copy_file_range", "flock",
"getdents", "getdents64", "utime", "utimes", "lutimes", "futimens", "utimensat", "futimesat",
"realpath",
];
const NET: &[&str] = &[
"socket", "setsockopt", "getsockopt", "bind", "listen", "accept", "accept4", "connect",
"shutdown", "send", "sendto", "sendmsg", "sendmmsg", "recv", "recvfrom", "recvmsg",
"recvmmsg", "getpeername", "getsockname", "getaddrinfo", "freeaddrinfo", "getnameinfo",
];
const EXEC: &[&str] = &[
"fork", "vfork", "clone", "clone3", "execl", "execlp", "execle", "execv", "execvp",
"execvpe", "execve", "execveat", "fexecve", "posix_spawn", "posix_spawnp", "system",
"popen", "pclose", "wait", "waitpid", "wait3", "wait4", "waitid",
];
const IPC: &[&str] = &[
"pipe", "pipe2", "mkfifo", "mkfifoat", "socketpair", "msgget", "msgsnd", "msgrcv", "msgctl",
"semget", "semop", "semtimedop", "semctl", "shmget", "shmat", "shmdt", "shmctl", "mq_open",
"mq_send", "mq_receive", "mq_timedsend", "mq_timedreceive", "mq_close", "mq_unlink",
];
const ENV: &[&str] = &["getenv", "secure_getenv", "setenv", "putenv", "unsetenv", "clearenv"];
const CLOCK: &[&str] = &[
"time", "gettimeofday", "clock_gettime", "clock_getres", "nanosleep", "clock_nanosleep",
"clock_settime", "settimeofday", "stime", "adjtime", "adjtimex", "clock_adjtime",
];
const RAND: &[&str] = &["getrandom", "getentropy", "arc4random", "arc4random_buf", "arc4random_uniform"];
if FS.contains(&f) {
return Some("Fs");
}
if NET.contains(&f) {
return Some("Net");
}
if EXEC.contains(&f) {
return Some("Exec");
}
if IPC.contains(&f) {
return Some("Ipc");
}
if ENV.contains(&f) {
return Some("Env");
}
if CLOCK.contains(&f) {
return Some("Clock");
}
if RAND.contains(&f) {
return Some("Rand");
}
return None;
}
{
let leaf = path.rsplit("::").next().unwrap_or(path);
if let Some(rest) = leaf.strip_prefix("sqlite3_") {
let _ = rest;
const DB: &[&str] = &[
"sqlite3_open", "sqlite3_open_v2", "sqlite3_open16", "sqlite3_close", "sqlite3_close_v2",
"sqlite3_exec", "sqlite3_step", "sqlite3_prepare", "sqlite3_prepare_v2",
"sqlite3_prepare_v3", "sqlite3_prepare16", "sqlite3_prepare16_v2", "sqlite3_prepare16_v3",
"sqlite3_get_table", "sqlite3_backup_init", "sqlite3_backup_step", "sqlite3_backup_finish",
"sqlite3_blob_open", "sqlite3_blob_read", "sqlite3_blob_write", "sqlite3_blob_reopen",
"sqlite3_load_extension", "sqlite3_wal_checkpoint", "sqlite3_wal_checkpoint_v2",
];
return DB.contains(&leaf).then_some("Db");
}
if leaf.starts_with("git_") {
const NET: &[&str] = &[
"git_clone", "git_remote_connect", "git_remote_connect_ext", "git_remote_fetch",
"git_remote_download", "git_remote_upload", "git_remote_push", "git_remote_ls",
"git_submodule_clone", "git_submodule_update",
];
const FS: &[&str] = &[
"git_repository_open", "git_repository_open_ext", "git_repository_open_bare",
"git_repository_init", "git_repository_init_ext", "git_repository_discover",
"git_checkout_tree", "git_checkout_head", "git_checkout_index", "git_index_read",
"git_index_write", "git_index_write_tree", "git_index_write_tree_to",
"git_index_add_bypath", "git_index_add_all", "git_odb_open", "git_odb_read",
"git_odb_write", "git_odb_open_wstream", "git_odb_open_rstream",
"git_blob_create_fromdisk", "git_blob_create_fromworkdir", "git_blob_create_from_disk",
"git_blob_create_from_workdir", "git_blob_create_from_stream", "git_commit_create",
"git_commit_create_v", "git_reference_create", "git_reference_set_target",
"git_reference_delete", "git_config_open_default", "git_config_open_ondisk",
"git_config_add_file_ondisk", "git_tag_create", "git_treebuilder_write",
"git_packbuilder_write",
];
if NET.contains(&leaf) {
return Some("Net");
}
if FS.contains(&leaf) {
return Some("Fs");
}
return None;
}
if leaf.starts_with("curl_") {
const NET: &[&str] = &[
"curl_easy_perform", "curl_easy_send", "curl_easy_recv", "curl_easy_upkeep",
"curl_multi_perform", "curl_multi_socket_action",
];
return NET.contains(&leaf).then_some("Net");
}
if let Some(op) = leaf.strip_prefix("SSL_") {
const SSL_NET: &[&str] = &[
"connect", "accept", "do_handshake", "read", "read_ex", "write", "write_ex", "peek",
"peek_ex", "shutdown",
];
return SSL_NET.contains(&op).then_some("Net");
}
}
if crate_name == "reqwest" || crate_name == "isahc" {
if path.ends_with("::send")
|| path.ends_with("::execute")
|| path == "reqwest::get"
|| path == "reqwest::blocking::get"
|| path == "isahc::get"
{
return Some("Net");
}
return None;
}
if crate_name == "ureq" && path.ends_with("::call") {
return Some("Net");
}
if crate_name == "curl"
&& (path.ends_with("::perform")
|| path.ends_with("::send")
|| path.ends_with("::recv")
|| path.ends_with("::upkeep")
|| path.ends_with("::action"))
{
return Some("Net");
}
if crate_name == "async_nats" {
if path.ends_with("::connect")
|| path.contains("::publish")
|| path.ends_with("::subscribe")
|| path.ends_with("::queue_subscribe")
|| path.contains("::request")
|| path.ends_with("::flush")
{
return Some("Net");
}
return None;
}
if crate_name == "lapin" {
if path.ends_with("::connect")
|| path.ends_with("::create_channel")
|| path.contains("::basic_")
|| path.contains("::queue_")
|| path.contains("::exchange_")
|| path.contains("::tx_")
|| path.ends_with("::confirm_select")
|| path.ends_with("::close")
{
return Some("Net");
}
return None;
}
if crate_name == "lettre" {
if path.ends_with("::send") || path.ends_with("::send_raw") {
return Some("Net");
}
return None;
}
if crate_name == "tungstenite" {
if path.ends_with("::connect")
|| path.ends_with("::read")
|| path.ends_with("::write")
|| path.ends_with("::send")
|| path.ends_with("::close")
|| path.ends_with("::flush")
|| path.ends_with("::read_message")
|| path.ends_with("::write_message")
{
return Some("Net");
}
return None;
}
if crate_name == "elasticsearch" && path.ends_with("::send") {
return Some("Net");
}
if crate_name == "tonic" {
if path.ends_with("::connect")
|| path.ends_with("::unary")
|| path.ends_with("::server_streaming")
|| path.ends_with("::client_streaming")
|| path.ends_with("::streaming")
{
return Some("Net");
}
return None;
}
if crate_name == "rdkafka" {
if path.ends_with("::send")
|| path.ends_with("::send_result")
|| path.ends_with("::recv")
|| path.ends_with("::poll")
|| path.ends_with("::subscribe")
|| path.ends_with("::commit")
|| path.ends_with("::commit_message")
|| path.ends_with("::commit_consumer_state")
|| path.ends_with("::store_offset")
|| path.ends_with("::seek")
|| path.ends_with("::fetch_metadata")
|| path.ends_with("::fetch_watermarks")
|| path.ends_with("::flush")
{
return Some("Net");
}
return None;
}
if crate_name.starts_with("cap_") {
if path.contains("::net::Unix") || path.contains("::os::") {
return Some("Ipc");
}
if path.contains("::net") {
return Some("Net");
}
if path.contains("::time") {
return Some("Clock");
}
if path.contains("::fs") || crate_name == "cap_tempfile" || crate_name == "cap_directories" {
return Some("Fs");
}
return None;
}
if path.starts_with("tokio::net::Unix")
|| path.starts_with("std::os::unix::net")
|| path.starts_with("async_std::os::unix::net")
|| path.starts_with("async_net::unix")
{
return Some("Ipc");
}
if crate_name == "pnet" || crate_name == "pnet_datalink" || crate_name == "pnet_transport" {
if path.ends_with("::channel") || path.ends_with("::transport_channel") {
return Some("Net");
}
return None;
}
if crate_name == "ignore" {
if path == "ignore::WalkBuilder::build"
|| path == "ignore::WalkBuilder::build_parallel"
|| path.ends_with("::WalkParallel::run")
|| path == "ignore::WalkBuilder::add_ignore"
{
return Some("Fs");
}
return None;
}
if crate_name == "notify" {
if path.ends_with("Watcher::new")
|| path.ends_with("::recommended_watcher")
|| path.ends_with("::watch")
|| path.ends_with("::unwatch")
{
return Some("Fs");
}
return None;
}
if path.ends_with("::to_socket_addrs")
|| path == "std::net::lookup_host"
|| path.ends_with("ToSocketAddrs::to_socket_addrs")
{
return Some("Net");
}
if path.starts_with("std::net::TcpStream")
|| path.starts_with("std::net::TcpListener")
|| path.starts_with("std::net::UdpSocket")
|| path.starts_with("tokio::net::")
{
if path.ends_with("::local_addr")
|| path.ends_with("::peer_addr")
|| path.ends_with("::nodelay")
|| path.ends_with("::ttl")
|| path.ends_with("::take_error")
{
return None;
}
return Some("Net");
}
if matches!(crate_name, "tokio_tcp" | "tokio_udp") {
return Some("Net");
}
if path.starts_with("async_std::net::")
|| path.starts_with("mio::net::")
|| crate_name == "async_net"
{
return Some("Net");
}
if DB_CRATES.contains(&crate_name) {
if matches!(crate_name, "postgres" | "tokio_postgres" | "deadpool_postgres" | "rusqlite") {
const PG: [&str; 19] = [
"::query", "::query_one", "::query_opt", "::query_raw", "::execute",
"::batch_execute", "::simple_query", "::prepare", "::prepare_typed",
"::copy_in", "::copy_out", "::transaction", "::connect",
"::query_row", "::query_map", "::query_and_then", "::execute_batch",
"::prepare_cached", "::query_typed",
];
if PG.iter().any(|v| path.ends_with(v)) {
return Some("Db");
}
if crate_name == "rusqlite"
&& (path.ends_with("::open")
|| path.ends_with("::open_in_memory")
|| path.ends_with("::open_with_flags"))
{
return Some("Db");
}
return None;
}
if crate_name == "redis"
&& (path.contains("Commands::")
|| path.contains("::get_connection")
|| path.contains("::get_async_connection")
|| path.contains("::get_multiplexed_async_connection")
|| (path.contains("ConnectionManager") && !path.contains("ConnectionManagerConfig")
&& !path.ends_with("::clone"))
|| path.ends_with("::query")
|| path.ends_with("::query_async")
|| path.ends_with("::req_command")
|| path.ends_with("::req_packed_command")
|| path.ends_with("::req_packed_commands"))
{
return Some("Db");
}
if crate_name == "mongodb" {
const MONGO: [&str; 27] = [
"::with_uri_str", "::connect", "::find", "::find_one", "::insert_one",
"::insert_many", "::update_one", "::update_many", "::delete_one",
"::delete_many", "::replace_one", "::aggregate", "::count_documents",
"::estimated_document_count", "::count", "::distinct", "::run_command",
"::find_one_and_update", "::find_one_and_delete", "::find_one_and_replace",
"::list_collections", "::list_collection_names", "::list_databases",
"::list_database_names", "::create_collection", "::create_index", "::watch",
];
if MONGO.iter().any(|v| path.ends_with(v)) {
return Some("Db");
}
return None;
}
if matches!(crate_name, "mysql" | "mysql_async") {
const MY: [&str; 16] = [
"::query", "::query_first", "::query_iter", "::query_map", "::query_fold",
"::query_drop", "::exec", "::exec_first", "::exec_iter", "::exec_map",
"::exec_fold", "::exec_drop", "::exec_batch", "::prep", "::ping", "::get_conn",
];
if MY.iter().any(|v| path.ends_with(v)) {
return Some("Db");
}
return None;
}
if crate_name == "sea_orm" {
if path.contains("sea_query") {
return None;
}
if path.ends_with("::all")
|| path.ends_with("::one")
|| path.ends_with("::count")
|| path.ends_with("::stream")
|| path.ends_with("::exec")
|| path.ends_with("::exec_with_returning")
|| path.ends_with("::exec_without_returning")
|| path.ends_with("::connect")
|| path.ends_with("::execute")
|| path.ends_with("::execute_unprepared")
|| path.ends_with("::query_one")
|| path.ends_with("::query_all")
|| path.ends_with("::fetch_page")
|| path.ends_with("::num_items")
|| path.contains("ActiveModelTrait::")
{
return Some("Db");
}
return None;
}
const VERBS: [&str; 19] = [
"::execute", "::query_row", "::query_map", "::query_one", "::fetch_one",
"::fetch_all", "::fetch_optional", "::fetch", "::fetch_many", "::connect",
"::acquire", "::begin", "::commit", "::rollback", "::load", "::load_iter",
"::first", "::get_result", "::get_results",
];
if VERBS.iter().any(|v| path.ends_with(v)) {
return Some("Db");
}
return None;
}
if let Some(m) = path
.strip_prefix("std::path::Path::")
.or_else(|| path.strip_prefix("std::path::PathBuf::"))
{
const STAT: &[&str] = &[
"metadata", "symlink_metadata", "canonicalize", "read_link", "read_dir", "exists",
"try_exists", "is_file", "is_dir", "is_symlink",
];
return STAT.contains(&m).then_some("Fs");
}
if path.starts_with("std::fs::")
|| path.starts_with("tokio::fs::")
|| path.starts_with("async_std::fs::")
|| crate_name == "async_fs"
|| crate_name == "fs_err"
{
return Some("Fs");
}
if crate_name == "memmap2" {
let m = path.rsplit("::").next().unwrap_or(path);
if m.starts_with("map")
|| m == "flush"
|| m == "flush_async"
|| m == "flush_range"
|| m == "flush_async_range"
|| m == "remap"
|| m.starts_with("make_")
|| m == "advise"
|| m == "advise_range"
|| m == "lock"
|| m == "unlock"
{
return Some("Fs");
}
return None;
}
if crate_name == "tempfile"
&& (path.ends_with("::tempfile")
|| path.ends_with("::tempfile_in")
|| path.ends_with("::tempdir")
|| path.ends_with("::tempdir_in")
|| path.ends_with("NamedTempFile::new")
|| path.ends_with("NamedTempFile::new_in")
|| path.ends_with("TempDir::new")
|| path.ends_with("TempDir::new_in")
|| path.ends_with("::persist")
|| path.ends_with("::persist_noclobber")
|| path.ends_with("::keep"))
{
return Some("Fs");
}
if crate_name == "glob" && (path.ends_with("::glob") || path.ends_with("::glob_with")) {
return Some("Fs");
}
if matches!(crate_name, "argon2" | "scrypt" | "pbkdf2" | "password_hash") {
if path.contains("SaltString::generate") {
return Some("Rand");
}
return None;
}
if crate_name == "bcrypt" {
if path.ends_with("::hash") || path.ends_with("::hash_with_result") {
return Some("Rand");
}
return None;
}
if crate_name == "rand_core" {
if path.contains("OsRng")
|| path.ends_with("::next_u32")
|| path.ends_with("::next_u64")
|| path.ends_with("::fill_bytes")
{
return Some("Rand");
}
return None;
}
if crate_name == "getrandom" {
return Some("Rand");
}
if crate_name == "fastrand" {
let m = path.rsplit("::").next().unwrap_or(path);
if m == "with_seed" || m == "fork" || m == "clone" {
return None;
}
return Some("Rand");
}
if crate_name == "rand" {
let rng_verb = path.ends_with("::gen")
|| path.ends_with("::gen_range")
|| path.ends_with("::gen_bool")
|| path.ends_with("::gen_ratio")
|| path.ends_with("::random")
|| path.ends_with("::random_range")
|| path.ends_with("::random_bool")
|| path.ends_with("::random_ratio")
|| path.ends_with("::random_iter") || path.ends_with("::gen_iter")
|| path.ends_with("::fill")
|| path.ends_with("::fill_bytes")
|| path.ends_with("::try_fill")
|| path.ends_with("::try_fill_bytes")
|| path.ends_with("::sample")
|| path.ends_with("::sample_iter")
|| path.ends_with("::next_u32")
|| path.ends_with("::next_u64")
|| path.ends_with("::thread_rng")
|| path.ends_with("::rng")
|| path.ends_with("::from_entropy")
|| path.ends_with("::from_os_rng");
let m = path.rsplit("::").next().unwrap_or(path);
let os_rng = path.contains("OsRng") && !matches!(m, "clone" | "fork" | "default");
if rng_verb || os_rng {
return Some("Rand");
}
return None;
}
if path.starts_with("std::process::Command")
|| path.starts_with("std::process::Child")
|| path.starts_with("tokio::process::Command")
|| path.starts_with("tokio::process::Child")
|| path.starts_with("async_std::process::Command")
|| path.starts_with("async_std::process::Child")
{
if path.ends_with("::get_program")
|| path.ends_with("::get_args")
|| path.ends_with("::get_envs")
|| path.ends_with("::get_current_dir")
|| path.ends_with("Child::id")
{
return None;
}
return Some("Exec");
}
if crate_name == "async_process" || crate_name == "portable_pty" {
let m = path.rsplit("::").next().unwrap_or(path);
if m.starts_with("get_") || m == "as_unix_command_line" {
return None;
}
if matches!(
m,
"default" | "new" | "piped" | "null" | "inherit" | "from_raw_fd"
| "arg" | "args" | "arg0" | "env" | "envs" | "env_clear" | "env_remove"
| "cwd" | "current_dir" | "rows" | "cols"
| "clone" | "fmt" | "eq" | "ne" | "hash"
) {
return None;
}
return Some("Exec");
}
if crate_name == "duct"
&& (path.ends_with("::run")
|| path.ends_with("::read")
|| path.ends_with("::start")
|| path.ends_with("::read_chars"))
{
return Some("Exec");
}
if path.starts_with("std::env::") {
return Some("Env");
}
if matches!(crate_name, "dotenvy" | "dotenv")
&& (path.ends_with("::dotenv")
|| path.ends_with("::dotenv_override")
|| path.ends_with("::from_path")
|| path.ends_with("::from_path_override")
|| path.ends_with("::from_filename")
|| path.ends_with("::from_filename_override")
|| path.ends_with("::from_read")
|| path.ends_with("::from_read_override")
|| path.ends_with("::load")
|| path.ends_with("::var")
|| path.ends_with("::vars"))
{
return Some("Env");
}
if (crate_name == "chrono" || path.starts_with("std::time::")) && path.ends_with("::now") {
return Some("Clock");
}
if crate_name == "time"
&& (path.ends_with("::now_utc") || path.ends_with("::now_local") || path.ends_with("::now"))
{
return Some("Clock");
}
if crate_name == "tracing" {
let m = path.rsplit("::").next().unwrap_or(path);
if m == "event"
|| m == "new_span"
|| m == "record"
|| m == "record_follows_from"
|| m == "enter"
|| m == "exit"
|| m == "in_scope"
|| m == "entered"
|| path.contains("::__macro_support")
|| path.contains("::__tracing")
|| path.contains("Subscriber::event")
|| path.contains("Subscriber::new_span")
|| path.contains("Subscriber::enter")
|| path.contains("Subscriber::exit")
{
return Some("Log");
}
return None;
}
if crate_name == "log" && path.contains("::__private_api") {
return Some("Log");
}
if crate_name == "rustc_lint"
&& (path.ends_with("::emit_span_lint")
|| path.ends_with("::span_lint")
|| path.ends_with("::span_lint_hir"))
{
return Some("Log");
}
if crate_name == "rustc_errors"
&& (path.ends_with("::emit")
|| path.ends_with("::emit_diagnostic")
|| path.ends_with("::emit_now"))
{
return Some("Log");
}
if crate_name == "arboard" {
let m = path.rsplit("::").next().unwrap_or(path);
if m == "new"
|| m == "get"
|| m == "set"
|| m == "clear"
|| m == "get_text"
|| m == "set_text"
|| m == "set_html"
|| m == "get_image"
|| m == "set_image"
|| m == "text"
|| m == "image"
|| m == "html"
{
return Some("Clipboard");
}
return None;
}
None
}
pub fn cap_from_name(name: &str) -> Option<&'static str> {
EFFECTS.iter().copied().find(|e| *e == name)
}
pub fn classify_command_head(cmd: &str) -> &'static [&'static str] {
match cmd.rsplit(['/', '\\']).next().unwrap_or(cmd) {
"curl" | "wget" | "http" | "ssh" | "scp" | "sftp" | "ftp" | "telnet" => &["Net"],
"psql" | "mysql" | "sqlite3" | "mongosh" | "mongo" | "redis-cli" | "cqlsh" | "influx" => &["Db"],
"candor" | "candor-run.sh" | "candor-scan" | "candor-query" | "candor-java"
| "candor-classify" | "candor-report" | "cargo-candor" => &["Env", "Fs"],
_ => &[],
}
}
pub fn is_cmd_builder_method(method: &str) -> bool {
matches!(
method,
"arg" | "args" | "arg0" | "env" | "envs" | "env_clear" | "env_remove" | "current_dir"
| "cwd" | "stdin" | "stdout" | "stderr" | "pre_exec" | "creation_flags" | "uid" | "gid"
| "groups" | "process_group"
)
}
pub fn is_cmd_naming_method(method: &str) -> bool {
matches!(method, "new" | "cmd")
}
pub fn is_net_establishing(method: &str) -> bool {
matches!(
method,
"connect"
| "connect_timeout"
| "get"
| "post"
| "put"
| "patch"
| "delete"
| "head"
| "request"
| "send_to"
| "lookup_host"
| "to_socket_addrs"
)
}
pub fn capstd_cap(crate_name: &str, type_name: &str) -> Option<&'static str> {
if !crate_name.starts_with("cap_") {
return None;
}
Some(match type_name {
"Dir" => "Fs",
"TcpListener" | "TcpStream" | "UdpSocket" | "Pool" => "Net",
"UnixListener" | "UnixStream" | "UnixDatagram" => "Ipc",
"SystemClock" | "MonotonicClock" => "Clock",
_ => return None,
})
}
pub fn tables_in_sql(sql: &str) -> Vec<String> {
const STMT: &[&str] =
&["select", "insert", "update", "delete", "create", "drop", "alter", "truncate", "merge", "replace", "with"];
const SKIP: &[&str] = &["only", "if", "not", "exists", "table"];
const STOP: &[&str] = &[
"select", "set", "where", "values", "on", "using", "group", "order", "by", "limit",
"returning", "as", "inner", "outer", "left", "right", "cross", "lateral", "natural",
"union", "all", "distinct", "case", "when", "null", "default", "skip", "nowait", "of",
"from", "join", "into", "update", "delete", "insert",
];
let cleaned: String = sql
.to_lowercase()
.chars()
.flat_map(|c| match c {
'(' | ')' | ';' => vec![' '],
',' => vec![' ', ',', ' '],
_ => vec![c],
})
.collect();
let toks: Vec<&str> = cleaned.split_whitespace().collect();
let Some(first) = toks.first() else { return Vec::new() };
if !STMT.contains(first) {
return Vec::new(); }
let ident = |t: &str| -> Option<String> {
let t = t.trim_matches(|c| matches!(c, '"' | '`' | '\''));
let mut chars = t.chars();
let ok_first = chars.next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
let ok_rest = t.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '.' | '$' | '"' | '`'));
(ok_first && ok_rest && !STOP.contains(&t)).then(|| t.replace(['"', '`'], ""))
};
let mut out: Vec<String> = Vec::new();
let mut push = |t: Option<String>| {
if let Some(t) = t {
if !out.contains(&t) {
out.push(t);
}
}
};
for (i, tok) in toks.iter().enumerate() {
let table_pos = match *tok {
"from" | "join" | "into" | "table" => true,
"update" | "truncate" => i == 0,
_ => false,
};
if !table_pos {
continue;
}
let mut j = i + 1;
while j < toks.len() && SKIP.contains(&toks[j]) {
j += 1;
}
let Some(next) = toks.get(j) else { continue };
let Some(first) = ident(next) else { continue };
push(Some(first));
while j + 2 < toks.len() && toks[j + 1] == "," {
let Some(more) = ident(toks[j + 2]) else { break };
push(Some(more));
j += 2;
}
}
out
}
#[cfg(test)]
mod tests {
#[test]
fn sql_table_extraction_is_conservative() {
use super::tables_in_sql as t;
assert_eq!(t("SELECT id FROM users WHERE x = 1"), vec!["users"]);
assert_eq!(t("select * from ledger.entries e join customers c on c.id = e.cid"),
vec!["ledger.entries", "customers"]);
assert_eq!(t("INSERT INTO audit_log (a) VALUES (?1)"), vec!["audit_log"]);
assert_eq!(t("UPDATE accounts SET v = ?"), vec!["accounts"]);
assert_eq!(t("DELETE FROM sessions WHERE id = ?"), vec!["sessions"]);
assert_eq!(t("CREATE TABLE IF NOT EXISTS cache (k TEXT)"), vec!["cache"]);
assert_eq!(t("TRUNCATE TABLE staging"), vec!["staging"]);
assert_eq!(t("SELECT * FROM jobs FOR UPDATE SKIP LOCKED"), vec!["jobs"]);
assert_eq!(t("SELECT * FROM (SELECT 1) q"), Vec::<String>::new());
assert_eq!(t("/tmp/some/path"), Vec::<String>::new());
assert_eq!(t("hello world from nowhere"), Vec::<String>::new());
assert_eq!(t("SELECT a FROM t1, t2, s.t3 WHERE x = 1"), vec!["t1", "t2", "s.t3"]);
assert_eq!(t("SELECT a FROM t1 a1, t2 WHERE x = 1"), vec!["t1"]);
assert_eq!(t("INSERT INTO t (a, b) VALUES (1, 2)"), vec!["t"]);
assert_eq!(t("SELECT a FROM t1, (SELECT 1) q"), vec!["t1"]);
}
use super::*;
#[test]
fn db_crates_are_calibrated() {
for c in DB_CRATES {
assert!(
CALIBRATED_CRATES.contains(&c),
"DB crate `{c}` is matched by classify() but missing from CALIBRATED_CRATES"
);
}
}
#[test]
fn calibrated_crates_are_live() {
for c in CALIBRATED_CRATES {
assert!(
CALIBRATION_PROBE_TAILS.iter().any(|t| classify(c, &format!("{c}{t}")).is_some()),
"calibrated crate `{c}` is matched by no path in classify() — dead list entry"
);
}
}
#[test]
fn classify_core_effects() {
assert_eq!(classify("std", "std::fs::read_to_string"), Some("Fs"));
assert_eq!(classify("std", "std::path::Path::symlink_metadata"), Some("Fs"));
assert_eq!(classify("std", "std::path::PathBuf::read_dir"), Some("Fs"));
assert_eq!(classify("std", "std::path::Path::exists"), Some("Fs"));
assert_eq!(classify("std", "std::path::Path::join"), None); assert_eq!(classify("std", "std::path::PathBuf::file_name"), None);
assert_eq!(classify("std", "std::path::Path::parent"), None);
assert_eq!(classify("std", "std::process::Command::new"), Some("Exec"));
assert_eq!(classify("std", "std::env::var"), Some("Env"));
assert_eq!(classify("reqwest", "reqwest::Client::execute"), Some("Net"));
assert_eq!(classify("reqwest", "reqwest::get"), Some("Net"));
assert_eq!(classify("reqwest", "reqwest::blocking::get"), Some("Net"));
assert_eq!(classify("reqwest", "reqwest::Client::get"), None);
assert_eq!(classify("reqwest", "reqwest::RequestBuilder::header"), None);
assert_eq!(classify("nix", "nix::fcntl::open"), Some("Fs"));
assert_eq!(classify("nix", "nix::sys::socket::connect"), Some("Net"));
assert_eq!(classify("nix", "nix::unistd::execvp"), Some("Exec"));
assert_eq!(classify("nix", "nix::unistd::write"), None); assert_eq!(classify("nix", "nix::unistd::getpid"), None); assert_eq!(classify("rustix", "rustix::time::clock_settime"), Some("Clock"));
assert_eq!(classify("rustix", "rustix::fs::symlink"), Some("Fs"));
assert_eq!(classify("rustix", "rustix::net::connect"), Some("Net"));
assert_eq!(classify("rustix", "rustix::io::read"), None); assert_eq!(classify("pnet", "pnet::datalink::channel"), Some("Net"));
assert_eq!(classify("pnet", "pnet::transport::transport_channel"), Some("Net"));
assert_eq!(classify("pnet_datalink", "pnet_datalink::channel"), Some("Net"));
assert_eq!(classify("pnet", "pnet::packet::ethernet::EthernetPacket::new"), None);
assert_eq!(classify("pnet_base", "pnet_base::MacAddr::new"), None);
assert_eq!(classify("ignore", "ignore::WalkBuilder::build_parallel"), Some("Fs"));
assert_eq!(classify("ignore", "ignore::WalkBuilder::build"), Some("Fs"));
assert_eq!(classify("ignore", "ignore::WalkParallel::run"), Some("Fs"));
assert_eq!(classify("ignore", "ignore::WalkBuilder::add_ignore"), Some("Fs")); assert_eq!(classify("ignore", "ignore::overrides::OverrideBuilder::build"), None); assert_eq!(classify("ignore", "ignore::gitignore::GitignoreBuilder::build"), None); assert_eq!(classify("ignore", "ignore::DirEntry::path"), None); assert_eq!(classify("notify", "notify::RecommendedWatcher::new"), Some("Fs"));
assert_eq!(classify("notify", "notify::PollWatcher::new"), Some("Fs"));
assert_eq!(classify("notify", "notify::recommended_watcher"), Some("Fs"));
assert_eq!(classify("notify", "notify::INotifyWatcher::watch"), Some("Fs"));
assert_eq!(classify("notify", "notify::Config::default"), None); assert_eq!(classify("notify", "notify::Event::new"), None); assert_eq!(classify("rusqlite", "rusqlite::Connection::execute"), Some("Db"));
assert_eq!(classify("rusqlite", "rusqlite::Connection::query_row"), Some("Db"));
assert_eq!(classify("rusqlite", "rusqlite::Statement::query_map"), Some("Db"));
assert_eq!(classify("rusqlite", "rusqlite::Connection::execute_batch"), Some("Db"));
assert_eq!(classify("rusqlite", "rusqlite::Connection::prepare_cached"), Some("Db"));
assert_eq!(classify("rusqlite", "rusqlite::Connection::open"), Some("Db"));
assert_eq!(classify("rusqlite", "rusqlite::Connection::open_in_memory"), Some("Db"));
assert_eq!(classify("postgres", "postgres::Client::open"), None);
assert_eq!(classify("tokio_postgres", "tokio_postgres::Client::query_typed"), Some("Db"));
assert_eq!(classify("diesel", "diesel::RunQueryDsl::first"), Some("Db"));
assert_eq!(classify("diesel", "diesel::RunQueryDsl::load_iter"), Some("Db"));
assert_eq!(classify("sqlx", "sqlx::query::Query::fetch_many"), Some("Db"));
assert_eq!(classify("sqlx", "sqlx::query"), None);
assert_eq!(classify("tracing", "tracing::event"), Some("Log"));
assert_eq!(classify("tracing", "tracing::Span::new_span"), Some("Log"));
assert_eq!(classify("tracing", "tracing::Span::record"), Some("Log"));
assert_eq!(classify("tracing", "tracing::Span::enter"), Some("Log"));
assert_eq!(classify("tracing", "tracing::Level::as_str"), None); assert_eq!(classify("tracing", "tracing::Span::is_disabled"), None); assert_eq!(classify("tracing", "tracing::Span::metadata"), None); assert_eq!(classify("tracing", "tracing::metadata::Level::TRACE"), None); assert_eq!(classify("tracing", "tracing::field::Field::name"), None); assert_eq!(classify("memmap2", "memmap2::MmapOptions::map"), Some("Fs"));
assert_eq!(classify("memmap2", "memmap2::MmapOptions::map_mut"), Some("Fs"));
assert_eq!(classify("memmap2", "memmap2::Mmap::flush"), Some("Fs"));
assert_eq!(classify("memmap2", "memmap2::MmapMut::make_read_only"), Some("Fs"));
assert_eq!(classify("memmap2", "memmap2::Mmap::len"), None); assert_eq!(classify("memmap2", "memmap2::Mmap::is_empty"), None); assert_eq!(classify("memmap2", "memmap2::Mmap::as_ptr"), None); assert_eq!(classify("memmap2", "memmap2::MmapOptions::new"), None); assert_eq!(classify("arboard", "arboard::Clipboard::new"), Some("Clipboard"));
assert_eq!(classify("arboard", "arboard::Clipboard::get_text"), Some("Clipboard"));
assert_eq!(classify("arboard", "arboard::Clipboard::set_text"), Some("Clipboard"));
assert_eq!(classify("arboard", "arboard::Clipboard::clear"), Some("Clipboard"));
assert_eq!(classify("arboard", "arboard::Error::to_string"), None); assert_eq!(classify("arboard", "arboard::Error::fmt"), None); assert_eq!(classify("arboard", "arboard::ImageData::to_owned_img"), None); assert_eq!(classify("fastrand", "fastrand::u32"), Some("Rand")); assert_eq!(classify("fastrand", "fastrand::Rng::usize"), Some("Rand"));
assert_eq!(classify("fastrand", "fastrand::Rng::shuffle"), Some("Rand"));
assert_eq!(classify("fastrand", "fastrand::Rng::new"), Some("Rand")); assert_eq!(classify("fastrand", "fastrand::Rng::with_seed"), None); assert_eq!(classify("fastrand", "fastrand::Rng::fork"), None); assert_eq!(classify("fastrand", "fastrand::Rng::clone"), None); assert_eq!(classify("portable_pty", "portable_pty::PtySystem::openpty"), Some("Exec"));
assert_eq!(classify("portable_pty", "portable_pty::SlavePty::spawn_command"), Some("Exec"));
assert_eq!(classify("portable_pty", "portable_pty::CommandBuilder::get_argv"), None); assert_eq!(classify("portable_pty", "portable_pty::CommandBuilder::get_cwd"), None); assert_eq!(classify("portable_pty", "portable_pty::PtySize::default"), None); assert_eq!(classify("portable_pty", "portable_pty::CommandBuilder::new"), None); assert_eq!(classify("async_process", "async_process::Command::spawn"), Some("Exec"));
assert_eq!(classify("async_process", "async_process::Command::output"), Some("Exec"));
assert_eq!(classify("async_process", "async_process::Stdio::piped"), None); assert_eq!(classify("async_process", "async_process::Stdio::null"), None); assert_eq!(classify("libc", "libc::open"), Some("Fs"));
assert_eq!(classify("libc", "libc::connect"), Some("Net"));
assert_eq!(classify("libc", "libc::read"), None); assert_eq!(classify("ffi", "ffi::sqlite3_step"), Some("Db"));
assert_eq!(classify("raw", "raw::git_remote_fetch"), Some("Net"));
assert_eq!(classify("raw", "raw::git_clone"), Some("Net"));
assert_eq!(classify("raw", "raw::git_submodule_clone"), Some("Net"));
assert_eq!(classify("raw", "raw::git_submodule_update"), Some("Net"));
assert_eq!(classify("raw", "raw::git_submodule_open"), None); assert_eq!(classify("curl_sys", "curl_sys::curl_easy_perform"), Some("Net"));
assert_eq!(classify("curl_sys", "curl_sys::curl_easy_send"), Some("Net"));
assert_eq!(classify("curl_sys", "curl_sys::curl_multi_perform"), Some("Net"));
assert_eq!(classify("curl_sys", "curl_sys::curl_multi_socket_action"), Some("Net"));
assert_eq!(classify("curl_sys", "curl_sys::curl_easy_setopt"), None); assert_eq!(classify("curl_sys", "curl_sys::curl_easy_init"), None); assert_eq!(classify("curl_sys", "curl_sys::curl_multi_wait"), None); assert_eq!(classify("curl", "curl::easy::Easy::perform"), Some("Net"));
assert_eq!(classify("curl", "curl::multi::Multi::perform"), Some("Net"));
assert_eq!(classify("curl", "curl::easy::Easy::send"), Some("Net"));
assert_eq!(classify("curl", "curl::easy::Easy::url"), None); assert_eq!(classify("curl", "curl::easy::Easy::timeout"), None); assert_eq!(classify("ffi", "ffi::SSL_connect"), Some("Net"));
assert_eq!(classify("serde", "serde::Serialize::serialize"), None);
assert_eq!(classify("std", "std::vec::Vec::push"), None);
assert_eq!(classify("std", "std::net::TcpStream::connect"), Some("Net"));
assert_eq!(classify("std", "std::net::TcpStream::local_addr"), None);
assert_eq!(classify("std", "std::net::TcpStream::nodelay"), None);
assert_eq!(classify("std", "std::net::TcpStream::ttl"), None);
assert_eq!(classify("std", "std::net::UdpSocket::peer_addr"), None);
assert_eq!(classify("std", "std::net::lookup_host"), Some("Net"));
assert_eq!(classify("std", "core::net::ToSocketAddrs::to_socket_addrs"), Some("Net"));
assert_eq!(classify("std", "std::process::Command::get_program"), None);
assert_eq!(classify("std", "std::process::Command::get_args"), None);
assert_eq!(classify("std", "std::process::Child::id"), None);
assert_eq!(classify("std", "std::process::Command::spawn"), Some("Exec"));
assert_eq!(classify("redis", "redis::aio::ConnectionManager::clone"), None);
assert_eq!(classify("redis", "redis::aio::ConnectionManager::send_packed_command"), Some("Db"));
assert_eq!(classify("sea_orm", "sea_orm::sea_query::Func::count"), None);
assert_eq!(classify("sea_orm", "sea_orm::sea_query::Condition::all"), None);
assert_eq!(classify("sea_orm", "sea_orm::Select::all"), Some("Db"));
}
#[test]
fn rand_osrng_handle_ops_are_pure_but_draws_are_rand() {
assert_eq!(classify("rand", "rand::rngs::OsRng::clone"), None);
assert_eq!(classify("rand", "rand::rngs::OsRng::default"), None);
assert_eq!(classify("rand", "rand::rngs::OsRng::fill_bytes"), Some("Rand")); assert_eq!(classify("rand", "rand::rngs::OsRng::next_u32"), Some("Rand"));
assert_eq!(classify("rand", "rand::Rng::gen"), Some("Rand")); assert_eq!(classify("rand", "rand::distributions::Uniform::new"), None); }
#[test]
fn redis_connection_manager_config_builder_is_pure() {
assert_eq!(classify("redis", "redis::aio::ConnectionManagerConfig::new"), None);
assert_eq!(classify("redis", "redis::aio::ConnectionManagerConfig::set_max_delay"), None);
assert_eq!(classify("redis", "redis::aio::ConnectionManager::new"), Some("Db"));
assert_eq!(classify("redis", "redis::Commands::get"), Some("Db"));
}
#[test]
fn pure_fd_transfer_is_not_an_effect() {
assert_eq!(classify("std", "std::net::TcpStream::from_raw_fd"), None);
assert_eq!(classify("std", "std::net::TcpStream::into_raw_fd"), None);
assert_eq!(classify("std", "std::net::TcpStream::as_raw_fd"), None);
assert_eq!(classify("std", "std::net::TcpListener::from_raw_fd"), None);
assert_eq!(classify("std", "std::net::UdpSocket::from_raw_socket"), None);
assert_eq!(classify("std", "std::fs::File::from_raw_fd"), None);
assert_eq!(classify("std", "std::fs::File::into_raw_fd"), None);
assert_eq!(classify("std", "std::fs::File::as_raw_handle"), None);
assert_eq!(classify("std", "std::os::unix::net::UnixStream::from_raw_fd"), None);
assert_eq!(classify("std", "std::os::unix::net::SocketAddr::from_pathname"), None);
assert_eq!(classify("tokio", "tokio::net::TcpStream::from_raw_fd"), None);
assert_eq!(classify("tokio", "tokio::net::TcpStream::into_std"), None); assert_eq!(classify("tokio", "tokio::fs::File::into_std"), None);
assert_eq!(classify("std", "std::net::TcpStream::connect"), Some("Net"));
assert_eq!(classify("std", "std::fs::File::open"), Some("Fs"));
assert_eq!(classify("std", "std::fs::read"), Some("Fs"));
assert_eq!(classify("std", "std::os::unix::net::UnixStream::connect"), Some("Ipc"));
assert_eq!(classify("tokio", "tokio::net::TcpStream::connect"), Some("Net"));
}
#[test]
fn command_head_refines_the_exec_cliff() {
use super::classify_command_head as h;
assert_eq!(h("curl"), &["Net"]);
assert_eq!(h("telnet"), &["Net"]);
assert_eq!(h("sftp"), &["Net"]);
assert_eq!(h("/usr/local/bin/psql"), &["Db"]); assert_eq!(h("mongo"), &["Db"]);
assert_eq!(h("cqlsh"), &["Db"]);
assert_eq!(h("candor-scan"), &["Env", "Fs"]);
assert_eq!(h("candor-run.sh"), &["Env", "Fs"]);
assert_eq!(h("some-unknown-tool"), &[] as &[&str]);
assert_eq!(h("make"), &[] as &[&str]);
assert_eq!(h("npm"), &[] as &[&str]);
assert_eq!(h("git"), &[] as &[&str]);
assert_eq!(h("rsync"), &[] as &[&str]);
assert!(is_cmd_builder_method("env") && is_cmd_builder_method("arg") && is_cmd_builder_method("current_dir"));
assert!(!is_cmd_builder_method("new")); assert!(!is_cmd_builder_method("cmd")); assert!(is_cmd_naming_method("new") && is_cmd_naming_method("cmd"));
assert!(!is_cmd_naming_method("get_env")); assert!(!is_cmd_naming_method("arg") && !is_cmd_naming_method("env") && !is_cmd_naming_method("current_dir"));
}
#[test]
fn net_establishing_allowlist() {
assert!(is_net_establishing("connect") && is_net_establishing("connect_timeout"));
assert!(is_net_establishing("get") && is_net_establishing("post") && is_net_establishing("request"));
assert!(is_net_establishing("send_to") && is_net_establishing("to_socket_addrs"));
assert!(!is_net_establishing("write") && !is_net_establishing("read") && !is_net_establishing("send"));
assert!(!is_net_establishing("flush") && !is_net_establishing("recv") && !is_net_establishing("peek"));
}
}