TempnamDirectoryFallbackUnchecked
warning native off by defaultGroup: Probable bugs
Detect tempnam result used for sensitive content under an explicitly enabled required-directory contract without verifying its parent directory.
Example
<?php
function bad($privateDir, $secret) { $path = tempnam($privateDir, 'job'); if ($path === false) { return; } file_put_contents($path, $secret); }Reported:
- line 2: Verify the temporary file remains in the required directory.
Detection
- Report tempnam result used for sensitive content under an explicitly enabled required-directory contract without verifying its parent directory.
- Resolve builtin calls and named arguments before checking the contract. Project-local callees are considered only when their bodies or immutable summaries establish the required facts. The default is disabled; enable only when the stated intent matches the application.
- For flow-dependent conditions, require a reachable source-to-use path with no intervening invalidation. Unknown calls, ambiguous aliases, recursion or analysis-budget exhaustion cannot establish definite facts.
Exceptions (no report)
Verified canonical parent, intentional system-temp fallback, unknown sensitivity and tempnam failure already handled are excluded.
The canonical parent is verified when
realpath(dirname($path))is compared with anotherrealpath(...)call, in either operand order, after thetempnamcall that produced$path, and the conditions guarding the write prove that comparison matched: an equality (===,==) known to hold or an inequality (!==,!=) known to fail where the write runs. That covers the body of anif(or the right operand of&&) on a match test, code after anifon a mismatch test whose body always leaves (return, throw, exit, break, continue), with or without other arms, and either test negated, compared with a boolean, combined through&&/||or kept in a local. Theelseof a match test is not covered.$pathmust hold the same value at the comparison and at the write, and a jump label between them voids the proof.Parentheses around the operands are ignored. The loose operators are accepted because a canonical path is an absolute string or
false, so numeric-string juggling cannot occur. A comparison of uncanonical values (dirname($path) !== $dir) proves nothing: symlinks and relative segments make equal directories differ and different ones match.A guard placed before the
tempnamcall that produced the written path verified a different file; the report is kept when the variable is assigned a new temporary file after the guard.The fallback is intentional when the directory argument of
tempnamis a builtinsys_get_temp_dir()call, directly or through a variable with that single known value: the system directory is the fallback target, so there is no other location to end up in. A path derived from it (sys_get_temp_dir() . '/private') is an ordinary required directory.Unresolved targets, incomplete required facts, suppressed findings and PHP versions lacking the referenced API or language feature produce no report. User declarations shadowing builtin names are not builtin contracts.
Fix
No automatic fix: fallback rejection and cleanup policy must be supplied explicitly.
PHP versions
Apply only on PHP versions supporting the referenced APIs and syntax (within PHP 5.3–8.5). Builtin behavior and argument contracts follow the configured PHP version. Extension-specific calls require resolved extension API symbols; do not treat same-named application functions as extension calls.
Options
None. Enable or disable this rule through normal rule configuration.
Configure
In custos.json:
{
"rules": {
"TempnamDirectoryFallbackUnchecked": {
"enabled": true
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
// @custos-ignore TempnamDirectoryFallbackUnchecked