State model analysis
Tracks resource lifecycle and authentication state through a function. Detects use-after-close, double-close, leaks, and unauthenticated access to privileged operations.
State analysis is on by default. Disable with scanner.enable_state_analysis = false. It runs in --mode full and --mode taint; AST-only mode skips it.
Rule IDs
| Rule ID | Severity |
|---|---|
state-use-after-close | High |
state-double-close | Medium |
state-resource-leak | Medium |
state-resource-leak-possible | Low |
state-unauthed-access | High |
What it detects
state-use-after-close: Resource transitions to CLOSED (via close, fclose, disconnect, …), then a use operation happens on it.
FILE *f = fopen("data.txt", "r");
fclose(f);
fread(buf, 1, 100, f); // state-use-after-close
state-double-close: Resource closed twice. Crashes or undefined behaviour on most runtimes.
state-resource-leak: Resource opened but never closed on any path through the function. Definite leak.
state-resource-leak-possible: Resource closed on some paths but not others. Lower confidence; often an early-return error path.
state-unauthed-access: A function recognised as a web handler reaches a privileged sink without an auth call on the path.
A function counts as a web handler if its name starts with handle_, route_, or api_ (sufficient on its own), or starts with serve_/process_ and the file uses web-shaped parameter names (request, req, ctx, res, response, w, writer, language-dependent). main is excluded.
Managed-resource suppression
Several language-specific cleanup patterns suppress leak findings:
| Pattern | Languages | Effect |
|---|---|---|
| RAII / Drop | Rust | All leak findings suppressed except alloc/dealloc |
| Smart pointers | C++ | make_unique/make_shared treated as managed; raw new/malloc still tracked |
defer | Go | defer f.Close() suppresses leak at exit |
with context manager | Python | with open(f) as f: suppresses leak for the bound name |
| try-with-resources | Java | TWR-bound resources suppressed |
What it can’t detect
- Cross-function resource ownership. Open in one function, close in another, leak gets reported in the opener. The most common FP source for leak detection.
- Factory / builder functions that return a resource for the caller to manage.
- Variable shadowing across scopes. Same name in inner and outer scope shares one symbol; an inner close masks an outer leak.
- Resources stored in collections. Handles in arrays / maps / channels and cleaned up via iteration are not tracked.
- Dynamic dispatch. Close called via trait object or interface may not be recognised.
- Type-state authentication.
AuthenticatedRequest<T>and similar Rust patterns are not recognised as auth.
Common false positives
| Scenario | Why | Mitigation |
|---|---|---|
| Factory returns a resource | Caller owns it | Known limitation |
| Framework-managed handles | Connection pool, request scope | Exclude framework code or downgrade |
| Variable name shadowing | Same name reused | Known limitation |
Per-language detection
| Language | Leak | Double-close | Use-after-close | Notes |
|---|---|---|---|---|
| C | yes | yes | yes | fopen/fclose, malloc/free, pthread_mutex_* |
| C++ | yes | yes | yes | C pairs plus new/delete; smart pointers suppressed |
| Python | yes | yes | yes | with suppressed; open, socket, connect |
| Go | yes | yes | yes | defer suppressed; os.Open / .Close |
| Rust | unsafe only | n/a | n/a | RAII suppresses everything except alloc/dealloc |
| JavaScript | yes | yes | partial | fs.openSync/closeSync |
| TypeScript | yes | yes | partial | Same as JS |
| PHP | yes | yes | partial | fopen/fclose, curl_init/curl_close, mysqli_* |
| Ruby | partial | partial | partial | File.open/close, TCPSocket |
| Java | limited | limited | limited | Constructor-callee matching is incomplete |
Tuning
nyx scan . --severity ">=MEDIUM" # Skip "possible" leaks (Low)
[scanner]
enable_state_analysis = true # default
excluded_directories = ["tests", "test", "spec"]
Recognised pairs
The state engine ships these acquire/release pairs. Custom pairs are not yet configurable; file an issue if you need one.
C / C++
| Acquire | Release |
|---|---|
fopen | fclose |
open | close |
socket | close |
malloc, calloc, realloc | free |
pthread_mutex_lock | pthread_mutex_unlock |
new, new[] (C++) | delete, delete[] |
Rust
| Acquire | Release |
|---|---|
File::open, File::create | drop, close |
TcpStream::connect | shutdown |
lock, read, write (Mutex/RwLock) | drop |
Java
| Acquire | Release |
|---|---|
new FileInputStream (and friends) | close |
getConnection | close |
new Socket | close |
Go, Python, JavaScript, Ruby, PHP follow language-idiomatic equivalents.
Use-after-close triggers
These operations on a closed resource fire state-use-after-close:
read, write, send, recv, fread, fwrite, fgets, fputs, fprintf, fscanf,
fflush, fseek, ftell, rewind, feof, ferror, fgetc, fputc, getc, putc,
ungetc, query, execute, fetch, sendto, recvfrom, ioctl, fcntl,
strcpy, strncpy, strcat, strncat, memcpy, memmove, memset, memcmp,
strcmp, strncmp, strlen, sprintf, snprintf