Skip to content

[Verdict Pattern] Over-hedges NMD when the missing fact is in the repo #121

Description

@vcth4nh

Root cause. Emits Needs-More-Data citing "missing context" that is actually present (the caller, route, or
config is one search away).

Dominant error type: Over-hedged — Needs More Data when locally decidable.

Target · file:line Engine · rule VHX → expected Resolved truth
dvcp · imgRead.c:132 CodeQL · cpp/path-injection NMD → FP sole caller is main, operator argv, no trust boundary
DVWA · vulnerabilities/exec/source/low.php:14 semgrep · exec-use NMD → TP $_REQUEST['ip']shell_exec in-file → RCE
NodeGoat · app/routes/contributions.js:34 CodeQL · js/code-injection NMD → TP route wired at app/routes/index.js:52eval() RCE
NodeGoat · server.js:78 CodeQL · js/clear-text-cookie NMD → TP cookie secure off + plain-HTTP server, both in-file (CWE-614)

VulnHunterX's verdicts (verbatim). What VHX actually emitted for each finding above (full guided-question transcript nested inside each):

imgRead.c:132 · cpp/path-injection — VHX said Needs More Data (Medium · 0.6)

No concrete path-sanitization defense is visible, so the prior high-confidence False Positive cannot be justified under the requested criteria. However, the visible code appears to be a CLI main using an operator-supplied filename, and whether this is exploitable as CWE-22 depends on missing privilege/deployment or other caller context establishing a real trust boundary. [second-opinion pass after 1-iter high-conf FP]

Full guided-question transcript
(a) No specific path-traversal defense is visible. The only visible check is `if (argc < 2)` before using `argv[1]`, which checks presence of an argument, not whether the path contains `../`, is canonicalized, or is constrained to an allowed directory. `argv[1]` is passed directly to `ProcessImage` at line 132, and `filename` is passed directly to `fopen(filename,"r")` in `ProcessImage` Statement 1.

(b) Because no path validation/canonicalization defense is visible, there is no visible mechanism that covers all reachable paths to the sink. The reachable path shown is: `argv[1]` at line 132 → `ProcessImage(filename)` → `fopen(filename,"r")` in `ProcessImage` Statement 1. The `argc < 2` check only ensures `argv[1]` exists; it does not restrict path traversal or enforce a directory boundary.

(c) The SAST rule `cpp/path-injection` looks for user-controlled input used in a file path/file access operation, allowing traversal or unintended file access. The tool flagged this because command-line input `argv[1]` at line 132 flows to `ProcessImage(filename)`, whose body calls `fopen(filename,"r")`. No cited defense checks the rule’s concern: there is no `../` rejection, `realpath`, allowlist directory check, or other path constraint visible.
vulnerabilities/exec/source/low.php:14 · exec-use — VHX said Needs More Data (Low · 0.3)

The reported rule scope is php/code-injection/CWE-94 via eval(), string assert(), or preg_replace('/e'), but the exact flagged line is shell_exec() and no PHP code-evaluation sink appears in the provided context. Although the visible data flow likely supports a separate OS command injection issue, that is a different vulnerability class and cannot make this specific CWE-94/php-code-injection finding a true positive. [verdict set to Needs More Data: a different rule/tool flagged the same issue at vulnerabilities/exec/source/low.php:14 as True Positive — tools disagree, manual review required]

Full guided-question transcript
Q1: The input comes from the HTTP request parameter `ip` via `$_REQUEST['ip']` on line 5. The code path is gated by `isset($_POST['Submit'])` on line 3.

Q2: No. The provided code does not use `eval()`, `assert()` with a string argument, or `preg_replace()` with the `/e` modifier. The exact flagged line 14 is `$cmd = shell_exec( 'ping  -c 4 ' . $target );`.

Q3: No validation or allowlist is visible before execution. `$target` is assigned directly from `$_REQUEST['ip']` on line 5 and then concatenated into `shell_exec()` on line 14, or line 10 on Windows. However, there is no `eval()` sink in this snippet.

Q4: For the reported php/code-injection/CWE-94 class, no PHP code such as `system()`, `file_get_contents()`, or data-exfiltration PHP code can be injected into an evaluator because no evaluated PHP string is present. Separately, shell commands could likely be injected through `$target` into `shell_exec()` on lines 10 and 14, but that is OS command injection, not the reported PHP code-injection sink.

Q5: No PHP string is evaluated. The visible executed shell command is constructed from fixed string literals plus user input: `'ping  ' . $target` on line 10 and `'ping  -c 4 ' . $target` on line 14.

Q6: No template engine, plugin system, sandboxed evaluator, or evaluator usage is visible in the provided context.

Exact flagged line and location: line 14 is `$cmd = shell_exec( 'ping  -c 4 ' . $target );`, located at top-level/function `<unknown>` in `vulnerabilities/exec/source/low.php`.

Additional caller context was unavailable: `caller:<unknown>` was provided as `<unavailable: out-of-snippet>`, so there is no further visible caller code to alter the analysis.
app/routes/contributions.js:34 · js/code-injection — VHX said Needs More Data (Medium · 0.7)

The dangerous pattern is clearly present and there is no visible in-function defense before the eval() sink. However, the provided additional context still does not establish that ContributionsHandler.handleContributionsUpdate is actually reachable from an HTTP route or external caller, so an exploitable path cannot be confirmed solely from the shown code. [No additional context available]

Full guided-question transcript
Q1: The apparent input source remains the HTTP request body: `req.body.roth` at `app/routes/contributions.js:34`. The added context does not change this. However, no callers were found for `handleContributionsUpdate` or `ContributionsHandler`, and no `module.exports`/`exports` global was found, so the external route/entrypoint that supplies `req` is still not visible.

Q2: Yes. The exact flagged line is `const roth = eval(req.body.roth);` at `app/routes/contributions.js:34`. It lives inside `this.handleContributionsUpdate`, which is defined within the `ContributionsHandler` function.

Q3: No validation or sandboxing is visible before `eval(req.body.roth)` at `app/routes/contributions.js:34`. The numeric validation occurs after the eval calls, so it cannot prevent code execution. The additional context about missing callers and missing exports does not reveal any upstream sanitizer or sandbox.

Q4: Yes. The active code appears to parse numeric contribution percentages, and the commented-out alternative shows `parseInt(req.body.preTax)`, `parseInt(req.body.afterTax)`, and `parseInt(req.body.roth)` as a safer non-code-executing approach. `JSON.parse()` or numeric parsing would be suitable for data parsing, but the active code uses `eval()`.

Q5: If this handler is reachable with attacker-controlled `req.body.roth`, an attacker could inject JavaScript such as `(function(){ /* malicious side effect */; return 1; })()` so code executes during `eval(req.body.roth)` and then returns a numeric value to survive later checks. This execution would happen before the validation logic.

Q6: The injected code would execute in the scope of `handleContributionsUpdate`, so visible in-scope values such as `req`, `res`, `next`, and `contributionsDAO` may be accessible. Availability of globals such as `process`, `require`, `fetch`, or `document` is not established in the provided context.
server.js:78 · js/clear-text-cookie — VHX said Needs More Data (High · 0.9)

The previous False Positive verdict cannot be supported under the stated rules because no concrete defense is visible. The provided snippet also does not contain the reported construct at flagged line 78, so the actual cookie-setting code is required before deciding whether the missing Secure/encryption protection is exploitable. [second-opinion pass after 1-iter high-conf FP]

Full guided-question transcript
Q1: No sensitive data is visible in the provided context. The snippet only checks `err`, logs a database connection error, logs the error object, and exits (lines 1-5).

Q2: No relevant storage sink is visible in the provided context. There is no `res.cookie`, `Set-Cookie` header, localStorage/sessionStorage/IndexedDB, file write, or database write in lines 1-5.

Q3: No encryption mechanism is visible in the provided context. There is no use of `crypto.createCipheriv`, Web Crypto API, or another vetted encryption API in lines 1-5.

Q4: Not applicable in the provided context because there is no localStorage access in lines 1-5.

Q5: Not determinable from the provided context. No storage medium containing sensitive data is visible in lines 1-5.

Q6: Not determinable from the provided context. No exposed sensitive data is visible in lines 1-5.

(a) Specific defense observed: None. There is no visible defense such as `secure: true` on a cookie, encryption before storage, or transport-security enforcement in lines 1-5.

(b) Coverage of all reachable paths to sink: Cannot be established. No sink is visible in lines 1-5, and the actual flagged line 78 is not present in the provided snippet.

(c) Why SAST flagged this finding: The `js/clear-text-cookie` rule looks for sensitive cookies being set without enforcing SSL encryption, typically a cookie missing the `Secure` attribute or equivalent protection. The provided lines 1-5 do not show the flagged cookie-setting construct or any defense checking it, so the finding cannot be verified from this context.

Why these verdicts are wrong. Every missing fact resolves with one repo search; VHX even confirmed
exec/source/low.php:14 as TP under the tainted-exec rule while hedging it under exec-use (see Issue 5).

Proposed fix. Before emitting NMD, require a repo search for the specific missing fact (caller / route /
config); decide TP/FP if it resolves locally. Reserve NMD for facts genuinely outside the repo. Done when
these 4 resolve.


Found while evaluating VulnHunterX against its intentionally-vulnerable benchmark targets.

Metadata

Metadata

Assignees

No one assigned

    Labels

    llm-verificationLLM verification / triage / questionstriage-qualityVerdict accuracy / false-positive reports

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions