* chore: Exclude CLAUDE.md from Cargo.toml * feat: add callgraph module and integrate into main analysis flow * feat: enhance CLI with new severity filtering and analysis modes * feat: update CHANGELOG with recent enhancements and fixes to severity filtering and output handling * feat: implement state-model dataflow analysis for resource lifecycle and auth state * feat: enhance diagnostic output formatting and add evidence structure * feat: implement attack surface ranking for diagnostics with scoring and sorting * feat: add comprehensive documentation for installation, usage, and rules reference * feat: add multiple language support for command execution and evaluation endpoints * feat: implement inline suppression for findings using `nyx:ignore` comments * feat: add confidence levels to AST patterns and update output structure * feat: implement low-noise prioritization system with category filtering, rollup grouping, and configurable budgets * feat: bump version to 0.4.0 and update changelog with new features and improvements * feat: add dead code allowances to various functions in mod.rs and real_world_tests.rs
6.4 KiB
CFG Structural Analysis
Summary
Nyx builds an intra-procedural control-flow graph (CFG) for each function and analyzes structural properties: whether sinks are guarded by sanitizers or validators, whether web handlers check authentication, whether resources are released on all exit paths, and whether error-handling code terminates properly.
These detectors use dominator analysis — they check whether a guard node dominates (must execute before) a sink node on the CFG.
Rule IDs
| Rule ID | Severity | Description |
|---|---|---|
cfg-unguarded-sink |
High/Medium | Sink reachable without a dominating guard or sanitizer |
cfg-auth-gap |
High | Web handler reaches privileged sink without auth check |
cfg-unreachable-sink |
Medium | Dangerous function in unreachable code |
cfg-unreachable-sanitizer |
Low | Sanitizer in unreachable code |
cfg-unreachable-source |
Low | Source in unreachable code |
cfg-error-fallthrough |
High/Medium | Error check doesn't terminate; dangerous code follows |
cfg-resource-leak |
Medium | Resource acquired but not released on all exit paths |
cfg-lock-not-released |
Medium | Lock acquired but not released on all exit paths |
What It Detects
Unguarded sinks (cfg-unguarded-sink)
A sink call (e.g. system(), eval(), Command::new()) is reachable from the function entry without passing through a guard or sanitizer that matches the sink's capability.
Auth gaps (cfg-auth-gap)
A function identified as a web handler (by parameter naming conventions like req, res, ctx, request) reaches a privileged sink (shell execution, file I/O) without a prior call to an authentication function (is_authenticated, require_auth, check_permission, etc.).
Unreachable security code (cfg-unreachable-*)
Sinks, sanitizers, or sources in dead code branches. This often indicates a refactoring error where security-critical code was accidentally made unreachable.
Error fallthrough (cfg-error-fallthrough)
An error check (null check, error return check) does not terminate the function or loop back. Execution continues to a dangerous operation on the error path.
Resource leaks (cfg-resource-leak, cfg-lock-not-released)
A resource acquisition call (e.g. File::open, fopen, socket, Lock) is not matched by a release call (e.g. close, fclose, unlock) on all exit paths from the function.
What It Cannot Detect
- Inter-procedural guards: If authentication is checked in a middleware function that calls this handler, the CFG detector cannot see it. It only analyzes one function at a time.
- Dynamic dispatch: Virtual method calls, function pointers, and closures are opaque to the CFG.
- Complex guard patterns: Only recognized guard function names are checked. Custom validation logic (e.g.
if password == expected) is not recognized as a guard. - Correct sanitization: The detector checks that some guard dominates the sink, not that the guard is correct. A guard that always passes would suppress the finding.
- Cross-function resource flows: If a file handle is opened in one function and closed in another, the detector will report a leak in the first function.
Common False Positives
| Scenario | Why it fires | Mitigation |
|---|---|---|
| Framework-level auth middleware | Handler doesn't call auth directly | Document as expected; suppress with severity filter |
| Resource closed via RAII/defer | Implicit cleanup not visible to CFG | Currently not detected; known limitation |
| Custom guard function name | Function not in the recognized guard list | Add the function name as a sanitizer in config |
| Test handlers | Intentionally skip auth in tests | Default non-prod downgrade reduces severity; or exclude test dirs |
Common False Negatives
| Scenario | Why it's missed |
|---|---|
| Auth in called function | Cross-function guards not tracked |
| Guard via type system | Type-level guarantees (e.g. Rust's AuthenticatedUser wrapper) not analyzed |
| Resource closed in finally/defer | Some cleanup patterns not recognized |
Confidence Signals
| Signal | Meaning |
|---|---|
| Evidence lists guard nodes | Shows which guards were checked and found missing |
| Sink has high capability | Shell execution or file I/O sinks are higher risk |
| Handler detection matched | Web handler identification is based on conventional parameter names |
Tuning and Noise Controls
Add custom guards/sanitizers
[[analysis.languages.python.rules]]
matchers = ["validate_request", "check_csrf"]
kind = "sanitizer"
cap = "all"
Add auth rules
Auth checks are recognized by function name. If your codebase uses non-standard names:
[[analysis.languages.javascript.rules]]
matchers = ["ensureLoggedIn", "requirePermission"]
kind = "sanitizer"
cap = "all"
Filter results
# Skip low-severity unreachable findings
nyx scan . --severity ">=MEDIUM"
Disable CFG analysis
nyx scan . --mode ast # AST patterns only
Examples
Unguarded sink
func handler(w http.ResponseWriter, r *http.Request) {
cmd := r.URL.Query().Get("cmd")
exec.Command("sh", "-c", cmd).Run() // cfg-unguarded-sink: no guard dominates
}
Auth gap
app.get('/admin/delete', (req, res) => {
// No is_authenticated() call
db.execute("DELETE FROM users WHERE id = " + req.params.id);
// cfg-auth-gap: web handler reaches privileged sink without auth
});
Resource leak
void process() {
FILE *f = fopen("data.txt", "r"); // acquire
if (error) {
return; // cfg-resource-leak: f not closed on this path
}
fclose(f);
}
Guard Rules
Nyx recognizes these function name patterns as guards:
| Pattern | Applies to |
|---|---|
validate*, sanitize* |
All sinks |
check_*, verify_*, assert_* |
All sinks |
shell_escape |
Shell execution sinks |
html_escape |
HTML/XSS sinks |
url_encode |
URL sinks |
which |
Shell execution (binary lookup) |
Auth rules
| Pattern | Category |
|---|---|
is_authenticated, require_auth, check_permission |
Common |
authorize, authenticate, require_login |
Common |
check_auth, verify_token, validate_token |
Common |
middleware.auth, auth.required |
Go |
isAuthenticated, checkPermission, hasAuthority, hasRole |
Java |