Skip to content

VNX-769: Uncontrolled File Descriptor Consumption

Reports Python loops and comprehensions that open a new file or socket handle per element and keep it open, which exhausts the process's descriptor limit on large input.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-769
Severity Low
SARIF level note
Kind sast
Languages Python
CWE CWE-769
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L
Tags file-descriptor-exhaustion cwe-769
Source internal/sast/rules/vnx-769.rego

Overview

VNX-769 maps to CWE-769: Uncontrolled File Descriptor Consumption (deprecated by MITRE in favour of CWE-774; the number is kept for rule identity). It reports Python code that opens one handle per element of its input and keeps it:

  • a handle assigned inside a for/while body (fh = open(p), io.open, codecs.open, gzip.open, bz2.open, lzma.open, tarfile.open, os.open, socket.socket(...), socket.create_connection(...)) that the loop body never closes (fh.close(), os.close(fd), with fh, contextlib.closing(fh)), returns or yields;
  • handles appended to a collection in a loop (handles.append(open(p))) with no close in the following lines;
  • a list, set or dict comprehension that opens a handle per element ([open(p) for p in paths]).

Not reported: with blocks, handles closed or yielded in the loop, handles chained straight into a read (open(p).read()), generator expressions, handles opened outside a loop (VNX-775 reports a missing close there), comments, string literals and test files.

Why This Matters

Every open handle consumes a file descriptor. When the number of iterations is controlled by input (uploaded archives, a list of hosts, a directory listing), enough elements exhaust the per-process limit and every later open, accept or connect fails with EMFILE.

What Gets Flagged

for path in paths:
    fh = open(path, encoding="utf-8")        # flagged
    records.append(json.load(fh))

files = [open(s) for s in shards]            # flagged

Not flagged:

for path in paths:
    with open(path, encoding="utf-8") as fh:
        records.append(json.load(fh))

Remediation

  1. Open handles in a with block inside the loop so each one closes before the next opens.
  2. When handles must be held together (a k-way merge), bound how many are open at once and close them through contextlib.ExitStack.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-769
// nosec vnx-769

Or record an ignore rule: vulnetix ignore add --rule vnx-769 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-769.rego, cli/internal/sast/docs/vnx-769.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.