nyx/src/cfg_analysis/rules.rs
Eli Peter a438886217
Python fp and docs updtes (#58)
* refactor: Update comments for clarity and add expectations.json files for performance metrics

* feat: Implement FP guard for JS/TS local-collection receivers to suppress missing ownership checks

* feat: Enhance Rust parameter handling to classify local collections and prevent false ownership checks

* refactor: Simplify code formatting for better readability in multiple files

* refactor: Improve UTF-8 sequence length handling and enhance clarity in loop iteration

* feat: Update Java and Python patterns to include new security rules

* refactor: Improve comment clarity and consistency across multiple Rust files

* refactor: Simplify code formatting for improved readability in integration tests and module files

* refactor: Improve comment formatting and enhance clarity in assertions across multiple files
2026-04-29 19:53:34 -04:00

805 lines
23 KiB
Rust

use crate::labels::Cap;
use crate::symbol::Lang;
/// A guard rule: functions that must dominate sinks to ensure safety.
pub struct GuardRule {
pub matchers: &'static [&'static str],
pub applies_to_sink_caps: Cap,
}
/// An auth rule: functions that perform authentication/authorization checks.
pub struct AuthRule {
pub matchers: &'static [&'static str],
}
/// An entry point rule: functions that serve as external-facing entry points.
pub struct EntryPointRule {
pub matchers: &'static [&'static str],
}
/// A resource acquire/release pair.
pub struct ResourcePair {
pub acquire: &'static [&'static str],
pub release: &'static [&'static str],
/// Patterns that look like acquire calls (e.g. `freopen` ends with `fopen`)
/// but should NOT be treated as acquisitions.
pub exclude_acquire: &'static [&'static str],
pub resource_name: &'static str,
/// Callee patterns that read/write/operate on this specific resource type,
/// triggering use-after-close if the handle is closed. Checked before the
/// global `RESOURCE_USE_PATTERNS` fallback.
pub use_patterns: &'static [&'static str],
}
// ── Guard rules ─────────────────────────────────────────────────────────
static COMMON_GUARDS: &[GuardRule] = &[
GuardRule {
matchers: &["validate", "sanitize"],
applies_to_sink_caps: Cap::all(),
},
GuardRule {
matchers: &["check_", "verify_", "assert_"],
applies_to_sink_caps: Cap::all(),
},
GuardRule {
matchers: &["shell_escape", "quote", "escape_shell"],
applies_to_sink_caps: Cap::SHELL_ESCAPE,
},
GuardRule {
matchers: &["html_escape", "encode_safe", "escape_html", "sanitize_html"],
applies_to_sink_caps: Cap::HTML_ESCAPE,
},
GuardRule {
matchers: &["url_encode", "encode_uri", "urlencode"],
applies_to_sink_caps: Cap::URL_ENCODE,
},
GuardRule {
matchers: &[
"which",
"resolve_binary",
"find_program",
"lookup_path",
"shutil.which",
],
applies_to_sink_caps: Cap::SHELL_ESCAPE,
},
];
pub fn guard_rules(_lang: Lang) -> &'static [GuardRule] {
// All languages share the common set for now; per-language
// overrides can be added via match arms when needed.
COMMON_GUARDS
}
// ── Auth rules ──────────────────────────────────────────────────────────
static COMMON_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
// Additional patterns
"ensureAuthenticated",
"ensureAuth",
"requireAuth",
"hasPermission",
"requireRole",
],
}];
static GO_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
"middleware.auth",
"auth.required",
// Additional patterns
"ensureAuthenticated",
"ensureAuth",
"requireAuth",
"hasPermission",
"requireRole",
],
}];
static JAVA_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
"isAuthenticated",
"checkPermission",
"hasAuthority",
"hasRole",
// Additional patterns
"ensureAuthenticated",
"ensureAuth",
"requireAuth",
"hasPermission",
"requireRole",
// Spring Security / JAX-RS annotation names (used by decorator
// detection, see `extract_auth_decorators` in src/cfg.rs).
"PreAuthorize",
"PostAuthorize",
"Secured",
"RolesAllowed",
"DenyAll",
"Authenticated",
],
}];
static JS_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
// JS/TS middleware & JWT
"ensureAuthenticated",
"ensureAuth",
"requireAuth",
"hasPermission",
"requireRole",
"checkRole",
"passport.authenticate",
"jwt.verify",
// NestJS-style decorators and guard class names (seeded by decorator
// arg extraction in `extract_auth_decorators`). `UseGuards` alone is
// too generic, we still match on guard *argument* identifiers here.
"Authenticated",
"AuthGuard",
"JwtAuthGuard",
"JwtGuard",
"RolesGuard",
"SessionGuard",
"PassportAuthGuard",
],
}];
static PYTHON_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
// Python-specific
"ensureAuthenticated",
"ensureAuth",
"requireAuth",
"hasPermission",
"requireRole",
"login_required",
"permission_required",
"has_permission",
"check_permissions",
// Common decorator names used by Flask-Login / Django / custom views
// (consumed by `extract_auth_decorators` on function_definition nodes).
"admin_required",
"staff_member_required",
"user_passes_test",
],
}];
static RUBY_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
// Devise / Rails controller-filter idioms (consumed by `before_action`
// decorator extraction).
"authenticate_user",
"authenticate_admin",
"require_user",
"require_admin",
"current_user",
],
}];
static PHP_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
// Symfony Security attributes (`#[IsGranted(..)]`, `#[Security(..)]`)
// and common guard idioms.
"IsGranted",
"Security",
],
}];
static CPP_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
// Custom C++ attributes, framework-defined, bare-name match.
"authenticated",
"require_auth",
"admin_only",
],
}];
static RUST_AUTH: &[AuthRule] = &[AuthRule {
matchers: &[
"is_authenticated",
"require_auth",
"check_permission",
"is_admin",
"authorize",
"authenticate",
"require_login",
"check_auth",
"verify_token",
"validate_token",
// Custom proc-macro attributes, framework-defined, bare-name match.
"authenticated",
"require_auth",
"admin_only",
],
}];
pub fn auth_rules(lang: Lang) -> &'static [AuthRule] {
match lang {
Lang::Go => GO_AUTH,
Lang::Java => JAVA_AUTH,
Lang::JavaScript | Lang::TypeScript => JS_AUTH,
Lang::Python => PYTHON_AUTH,
Lang::Ruby => RUBY_AUTH,
Lang::Php => PHP_AUTH,
Lang::Cpp => CPP_AUTH,
Lang::Rust => RUST_AUTH,
_ => COMMON_AUTH,
}
}
// ── Entry point rules ───────────────────────────────────────────────────
static COMMON_ENTRY_POINTS: &[EntryPointRule] = &[EntryPointRule {
matchers: &[
"main",
"handle_*",
"route_*",
"api_*",
"serve_*",
"process_*",
],
}];
static GO_ENTRY_POINTS: &[EntryPointRule] = &[EntryPointRule {
matchers: &[
"main",
"handle_*",
"handler_*",
"route_*",
"api_*",
"serve_*",
"process_*",
"ServeHTTP",
],
}];
static PYTHON_ENTRY_POINTS: &[EntryPointRule] = &[EntryPointRule {
matchers: &[
"main",
"handle_*",
"route_*",
"api_*",
"serve_*",
"process_*",
"view_*",
],
}];
pub fn entry_point_rules(lang: Lang) -> &'static [EntryPointRule] {
match lang {
Lang::Go => GO_ENTRY_POINTS,
Lang::Python => PYTHON_ENTRY_POINTS,
_ => COMMON_ENTRY_POINTS,
}
}
// ── Resource pairs ──────────────────────────────────────────────────────
static C_RESOURCES: &[ResourcePair] = &[
ResourcePair {
acquire: &["malloc", "calloc", "realloc"],
release: &["free"],
exclude_acquire: &[],
resource_name: "memory",
use_patterns: &[],
},
ResourcePair {
acquire: &["fopen", "fdopen", "curlx_fopen", "curlx_fdopen"],
release: &["fclose", "curlx_fclose"],
exclude_acquire: &["freopen", "curlx_freopen"],
resource_name: "file handle",
use_patterns: &[],
},
ResourcePair {
acquire: &["open"],
release: &["close"],
exclude_acquire: &["freopen", "curlx_freopen"],
resource_name: "file descriptor",
use_patterns: &[],
},
ResourcePair {
acquire: &["pthread_mutex_lock"],
release: &["pthread_mutex_unlock"],
exclude_acquire: &[],
resource_name: "mutex",
use_patterns: &[],
},
];
static CPP_RESOURCES: &[ResourcePair] = &[
// Inherited from C
ResourcePair {
acquire: &["malloc", "calloc", "realloc"],
release: &["free"],
exclude_acquire: &[],
resource_name: "memory",
use_patterns: &[],
},
ResourcePair {
acquire: &["fopen", "fdopen", "curlx_fopen", "curlx_fdopen"],
release: &["fclose", "curlx_fclose"],
exclude_acquire: &["freopen", "curlx_freopen"],
resource_name: "file handle",
use_patterns: &[],
},
ResourcePair {
acquire: &["open"],
release: &["close"],
exclude_acquire: &["freopen", "curlx_freopen"],
resource_name: "file descriptor",
use_patterns: &[],
},
ResourcePair {
acquire: &["pthread_mutex_lock"],
release: &["pthread_mutex_unlock"],
exclude_acquire: &[],
resource_name: "mutex",
use_patterns: &[],
},
// C++ new/delete (callee normalized to "new"/"delete" in cfg.rs)
ResourcePair {
acquire: &["new"],
release: &["delete"],
exclude_acquire: &[],
resource_name: "heap object",
use_patterns: &[],
},
];
static GO_RESOURCES: &[ResourcePair] = &[
ResourcePair {
acquire: &["os.Open", "os.Create", "os.OpenFile"],
release: &[".Close"],
exclude_acquire: &[],
resource_name: "file handle",
use_patterns: &[],
},
ResourcePair {
acquire: &[".Lock"],
release: &[".Unlock"],
exclude_acquire: &[],
resource_name: "mutex",
use_patterns: &[],
},
// Additional patterns
ResourcePair {
acquire: &["sql.Open"],
release: &[".Close"],
exclude_acquire: &[],
resource_name: "db connection",
use_patterns: &[".Query", ".Exec", ".Prepare", ".Begin"],
},
ResourcePair {
acquire: &["net.Listen"],
release: &[".Close"],
exclude_acquire: &[],
resource_name: "listener",
use_patterns: &[".Accept", ".Addr"],
},
];
static RUST_RESOURCES: &[ResourcePair] = &[
// Rust uses RAII, but unsafe alloc/dealloc is a pattern
ResourcePair {
acquire: &["alloc"],
release: &["dealloc"],
exclude_acquire: &[],
resource_name: "raw memory",
use_patterns: &[],
},
];
static JAVA_RESOURCES: &[ResourcePair] = &[
ResourcePair {
acquire: &[
"FileInputStream",
"FileOutputStream",
"BufferedReader",
"openConnection",
],
release: &[".close"],
exclude_acquire: &[],
resource_name: "stream/connection",
use_patterns: &[".read", ".write", ".flush", ".available"],
},
ResourcePair {
acquire: &["DriverManager.getConnection", "getConnection"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "db connection",
use_patterns: &[
".executeQuery",
".executeUpdate",
".createStatement",
".prepareStatement",
],
},
ResourcePair {
acquire: &["Socket"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "socket",
use_patterns: &[".getInputStream", ".getOutputStream", ".connect"],
},
// Additional patterns
ResourcePair {
acquire: &["prepareStatement"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "prepared statement",
use_patterns: &[".executeQuery", ".executeUpdate", ".setString", ".setInt"],
},
ResourcePair {
acquire: &["executeQuery"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "result set",
use_patterns: &[".next", ".getString", ".getInt", ".getBoolean"],
},
ResourcePair {
acquire: &["ServerSocket"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "server socket",
use_patterns: &[".accept", ".bind"],
},
ResourcePair {
acquire: &["DatagramSocket"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "datagram socket",
use_patterns: &[".send", ".receive"],
},
ResourcePair {
acquire: &["RandomAccessFile"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "random access file",
use_patterns: &[".read", ".write", ".seek", ".length"],
},
ResourcePair {
acquire: &["Channel.open", "FileChannel.open", "SocketChannel.open"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "NIO channel",
use_patterns: &[".read", ".write", ".position"],
},
];
static PYTHON_RESOURCES: &[ResourcePair] = &[
ResourcePair {
acquire: &["open"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "file handle",
use_patterns: &[],
},
ResourcePair {
acquire: &["socket.socket", "socket"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "socket",
use_patterns: &[],
},
ResourcePair {
acquire: &["connect", "cursor"],
release: &[".close"],
exclude_acquire: &["signal.connect", "event.connect", ".register"],
resource_name: "db connection",
use_patterns: &[],
},
ResourcePair {
acquire: &["threading.Lock", "threading.RLock"],
release: &[".release"],
exclude_acquire: &[],
resource_name: "mutex",
use_patterns: &[],
},
// Additional patterns
ResourcePair {
acquire: &["urllib.request.urlopen", "urlopen"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "url handle",
use_patterns: &[".read", ".readline", ".readlines"],
},
ResourcePair {
acquire: &[
"http.client.HTTPConnection",
"HTTPConnection",
"HTTPSConnection",
],
release: &[".close"],
exclude_acquire: &[],
resource_name: "http connection",
use_patterns: &[".request", ".getresponse"],
},
ResourcePair {
acquire: &["sqlite3.connect"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "sqlite connection",
use_patterns: &[".execute", ".cursor", ".commit"],
},
ResourcePair {
acquire: &["tempfile.NamedTemporaryFile", "NamedTemporaryFile"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "temp file",
use_patterns: &[".write", ".read", ".seek"],
},
ResourcePair {
acquire: &["zipfile.ZipFile", "ZipFile"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "zip file",
use_patterns: &[".read", ".write", ".extractall", ".namelist"],
},
];
static RUBY_RESOURCES: &[ResourcePair] = &[
ResourcePair {
acquire: &["File.open", "File.new", "open"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "file handle",
use_patterns: &[
".read",
".write",
".gets",
".puts",
".each_line",
".readline",
".readlines",
".sysread",
".syswrite",
],
},
ResourcePair {
acquire: &[
"TCPSocket.new",
"UDPSocket.new",
"TCPServer.new",
"UNIXSocket.new",
],
release: &[".close"],
exclude_acquire: &[],
resource_name: "socket",
use_patterns: &[".read", ".write", ".send", ".recv", ".gets", ".puts"],
},
ResourcePair {
acquire: &[".lock"],
release: &[".unlock"],
exclude_acquire: &[],
resource_name: "mutex",
use_patterns: &[],
},
ResourcePair {
acquire: &[
"PG.connect",
"PG::Connection.new",
"Sequel.connect",
"Mysql2::Client.new",
"SQLite3::Database.new",
],
release: &[".close", ".disconnect"],
exclude_acquire: &[],
resource_name: "db connection",
use_patterns: &[".exec", ".query", ".exec_params", ".prepare", ".execute"],
},
// Additional patterns
ResourcePair {
acquire: &["Net::HTTP.start"],
release: &[".finish"],
exclude_acquire: &[],
resource_name: "http connection",
use_patterns: &[".get", ".post", ".request"],
},
ResourcePair {
acquire: &["Tempfile.new", "Tempfile.open"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "temp file",
use_patterns: &[".write", ".read", ".path"],
},
];
static PHP_RESOURCES: &[ResourcePair] = &[
ResourcePair {
acquire: &["fopen"],
release: &["fclose"],
exclude_acquire: &["freopen"],
resource_name: "file handle",
use_patterns: &[],
},
ResourcePair {
acquire: &["mysqli_connect", "mysqli"],
release: &["mysqli_close", ".close"],
exclude_acquire: &[],
resource_name: "db connection",
use_patterns: &["mysqli_query", "mysqli_fetch_array", ".query", ".fetch"],
},
ResourcePair {
acquire: &["curl_init"],
release: &["curl_close"],
exclude_acquire: &[],
resource_name: "curl handle",
use_patterns: &["curl_exec", "curl_getinfo", "curl_setopt"],
},
// Additional patterns
ResourcePair {
acquire: &["pg_connect"],
release: &["pg_close"],
exclude_acquire: &[],
resource_name: "pg connection",
use_patterns: &["pg_query", "pg_fetch_array", "pg_fetch_row"],
},
ResourcePair {
acquire: &["fsockopen", "pfsockopen"],
release: &["fclose"],
exclude_acquire: &[],
resource_name: "socket",
use_patterns: &["fwrite", "fread", "fgets"],
},
ResourcePair {
acquire: &["stream_socket_client"],
release: &["fclose"],
exclude_acquire: &[],
resource_name: "stream socket",
use_patterns: &["fwrite", "fread", "stream_get_contents"],
},
];
static JS_RESOURCES: &[ResourcePair] = &[
ResourcePair {
acquire: &["fs.open", "fs.openSync"],
release: &["fs.close", "fs.closeSync"],
exclude_acquire: &[],
resource_name: "file descriptor",
use_patterns: &[
"fs.readSync",
"fs.writeSync",
"fs.fstatSync",
"fs.ftruncateSync",
"fs.fsyncSync",
],
},
ResourcePair {
acquire: &["createReadStream", "createWriteStream"],
release: &[".close", ".destroy"],
exclude_acquire: &[],
resource_name: "stream",
use_patterns: &[".pipe", ".resume", ".write", ".read", ".push"],
},
// Additional patterns
ResourcePair {
acquire: &["net.createConnection", "net.connect"],
release: &[".end", ".destroy"],
exclude_acquire: &[],
resource_name: "net socket",
use_patterns: &[".write", ".read", ".pipe"],
},
ResourcePair {
acquire: &["http.request", "https.request"],
release: &[".end", ".destroy"],
exclude_acquire: &[],
resource_name: "http request",
use_patterns: &[".write"],
},
ResourcePair {
acquire: &["WebSocket"],
release: &[".close"],
exclude_acquire: &[],
resource_name: "websocket",
use_patterns: &[".send"],
},
ResourcePair {
acquire: &["mysql.createConnection", "mysql.createPool"],
release: &[".end", ".destroy"],
exclude_acquire: &[],
resource_name: "mysql connection",
use_patterns: &[".query", ".execute"],
},
ResourcePair {
acquire: &["pg.Pool", "pg.Client"],
release: &[".end", ".release"],
exclude_acquire: &[],
resource_name: "pg connection",
use_patterns: &[".query"],
},
];
pub fn resource_pairs(lang: Lang) -> &'static [ResourcePair] {
match lang {
Lang::C => C_RESOURCES,
Lang::Cpp => CPP_RESOURCES,
Lang::Go => GO_RESOURCES,
Lang::Rust => RUST_RESOURCES,
Lang::Java => JAVA_RESOURCES,
Lang::Python => PYTHON_RESOURCES,
Lang::Ruby => RUBY_RESOURCES,
Lang::Php => PHP_RESOURCES,
Lang::JavaScript | Lang::TypeScript => JS_RESOURCES,
}
}