Skip to content

VNX-775: Missing Release of File Descriptor or Handle after Effective Lifetime

Reports Python functions that assign a file or socket handle to a local name and never close it, return it or hand it off in that function.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-775
Severity Low
SARIF level note
Kind sast
Languages Python
CWE CWE-775
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 handle-leak cwe-775
Source internal/sast/rules/vnx-775.rego

Overview

VNX-775 maps to CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime. It reports a Python function that assigns a handle to a local name (x = open(...), io.open, codecs.open, gzip.open, bz2.open, lzma.open, tarfile.open, os.open, socket.socket(...), socket.create_connection(...)) and never releases it in that function: no x.close(), os.close(x), with x, contextlib.closing(x) or enter_context(x).

Not reported: handles opened in a with statement; handles the function returns, yields or wraps (return x, yield from x, return Wrapper(x), ctx.wrap_socket(x)); webbrowser.open(url), which returns a flag rather than a handle; handles stored on an object or in a collection (self.sock = ..., self.files.append(x)); handles chained straight into a read (open(p).read()); module-level handles (program lifetime); handles opened inside a loop (VNX-769); comments, string literals and test files.

Why This Matters

The descriptor stays open until the garbage collector finalises the object, which is not immediate on PyPy, is delayed by reference cycles and tracebacks held by an exception handler, and never happens for the integer os.open returns. A request handler that leaks one handle per call eventually fails every request with EMFILE.

What Gets Flagged

def fetch_banner(host, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # flagged
    s.connect((host, port))
    return s.recv(1024).decode()

def load_config(path):
    fh = open(path, encoding="utf-8")                      # flagged
    return json.load(fh)

Not flagged:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((host, port))

def make_socket(host, port):
    s = socket.socket()
    s.connect((host, port))
    return s

Remediation

Open handles with with, or close them in a finally block; use contextlib.closing for objects that only have close().

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-775
// nosec vnx-775

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

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