SkillTotal

Is nltk safe?

Some risk - review before installing
Notable — review in context (capabilities are not malware):
  • Python shell/command execution
  • Python dynamic code execution
  • Unsafe deserialization

nltk-3.10.3 is an AI python_package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 7 risky constructs are reported for review. It can: dynamic code execution, filesystem read, filesystem write, network egress and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 40/100 (medium).

nltk-3.10.3 3.10.3

python_package · pypi:nltk
MEDIUM
40
/ 100 risk score
Snapshot · scanned Aug 13, 2026 · nltk-3.10.3@3.10.3 · engine 0.38.1 / ruleset 42

Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of nltk's authors. Report a false positive.

Capabilities — what this component can do (not a risk score):
dynamic code executionfilesystem readfilesystem writenetwork egressshell execution

Behavioral traits

How this component maps to the CSA agentic threat model. Descriptive — it never affects the risk score.

Execution authority
Tool Access Control / Direct Tool Access
Filesystem reach
Tool Execution Context
Network egress
Interaction & Communication / Direct Communication
Embedded credential access
Tool Execution Context / Agent Service Identity
Unsafe deserialization
General Protections / Input Validation

Findings (7)

HIGHUnsafe deserializationST-DESERIALIZE-PY

It loads data with a format that can rebuild arbitrary objects (e.g. pickle, or unsafe YAML).

db_out = shelve.open(db, "n")
db_in = shelve.open(db)

Why it matters: Feeding such a loader untrusted data can execute code hidden inside that data.

Fix: Deserialize untrusted data with a safe format/loader: JSON, or yaml.safe_load / Loader=SafeLoader. Reserve pickle/marshal for data you fully control.

HIGHPython dynamic code executionST-DYN-PY

The code turns strings into live code at runtime (eval / new Function / exec).

funcopy = eval(src, dict(_wrapper_=wrapper))
dec_func = eval(src, dict(_func_=func, _call_=caller))
return eval(s[start_position : match.end()]), match.end()
w = eval("numpy." + window + "(window_len)")

Why it matters: If those strings aren't fixed and trusted, they become a way to run arbitrary code.

Fix: Avoid evaluating dynamically constructed code; if unavoidable, ensure the input is a trusted constant and never derived from external data.

HIGHSensitive path / secret-location referenceST-SENS-PATH

The component references credential locations like ~/.ssh or .aws/credentials.

In practice, normal public URLs continue to work, but URLs such as ``https://127.0.0.1/...``, ``https://10.0.0.1/...`` and ``https://169.254.169.254/...`` are rejected.

Why it matters: Touching secret locations is a common first step before stealing them — confirm why it's needed.

Fix: Verify why the component references credential locations; reading these is a common precursor to secret exfiltration.

HIGHPython shell/command executionST-SHELL-PY

The component can run operating-system commands or spawn processes.

p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
p = Popen(_senna_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
p = subprocess.Popen(cmd, stdout=sys.stdout)
p = subprocess.Popen(
        ["svn", "status", "-v", filename],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
p = subprocess.Popen(
            cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE
        )
p = subprocess.Popen(
            final_cmd,
            stdin=stdin,
            stdout=stdout,
            stderr=stderr,
            universal_newlines=True,
        )
p = subprocess.Popen(
                    ["which", alternative],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                )
proc = subprocess.run(
                [dot_binary, "-T%s" % t],
                capture_output=True,
                input=dot_string,
                text=True,
            )
proc = subprocess.run(
                [dot_binary, "-T%s" % t],
                input=bytes(dot_string, encoding="utf8"),
            )
p = subprocess.Popen(cmd, stdout=output, stderr=output, cwd=cwd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(
                cmd,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
self._hunpos = Popen(
            [self._hunpos_bin, self._hunpos_model],
            shell=False,
            stdin=PIPE,
            stdout=PIPE,
            stderr=PIPE,
        )
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process = subprocess.Popen(
                [dot_binary, "-T%s" % output_format],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )

Why it matters: Powerful and often legitimate — confirm the commands aren't built from untrusted input.

Fix: Confirm the command and its arguments are fully controlled and not derived from untrusted input; avoid shell=True.

MEDIUMPython filesystem readST-FS-PY-READ

The component reads files from disk.

with open(version_file) as infile:
with open(filename, "rb") as infile:
with open(filename, "rb") as infile:
with open(filename, "rb") as infile:
with open(filename) as infile:
with open(filename) as infile:
with open(usp) as infile:
"text": _ieer_read_text(m.group("text"), root_label),
"headline": _ieer_read_text(m.group("headline"), root_label),
return _ieer_read_text(s, root_label)
with open(weightfile_name) as weightfile:
with self.abspath(framefile).open() as fp:
with self.abspath(framefile).open() as fp:
with self.abspath(framefile).open() as fp:
with self.abspath(framefile).open() as fp:
with self.abspath(fileid).open() as fp:
return find(path_).open()
return find(local_path).open()
with open(file, "rb") as infile:
with open(file, "rb") as infile:
with open(options.file) as infile:
with open(output_file_name) as infile:
with open(modelFile, "rb") as f:
with open(path) as fin:
with open(cache_baseline_tagger, "rb") as print_rules:

Why it matters: Usually legitimate, but worth confirming it can't be steered into reading sensitive files.

Fix: Confirm which files are read and that paths cannot be influenced by untrusted input to reach sensitive locations.

MEDIUMPython filesystem write/deleteST-FS-PY-WRITE

The component writes or deletes files on disk.

with open(filename, "wb") as outfile:
with open(filename, "wb") as outfile:
with open(filename, "wb") as outfile:
with open(filename, "w") as outfile:
with open(filename, "w") as outfile:
with open(filename, "w") as outfile:
logfile = open(logfilename, "a", 1)  # 1 means 'line buffering'
with open(trainfile_name, "w") as trainfile:
os.remove(trainfile_name)
os.remove(trainfile_name)
os.remove(weightfile_name)
with open(f"{tab_dir}/weights.txt", "w") as f:
with open(f"{tab_dir}/mapping.tab", "w") as f:
with open(f"{tab_dir}/labels.txt", "w") as f:
with open(f"{tab_dir}/alwayson.tab", "w") as f:
os.remove(os.path.join(temp_dir, f))
os.remove(os.path.join(temp_dir, f))
outfile = open(outfile, "w")
os.remove(self.write_file.name)
os.remove(os.path.join(root, name))
with open(filename, "wb") as f:
os.remove(input_file.name)
os.unlink(input_file_name)

Why it matters: Usually legitimate, but worth confirming the paths can't be controlled by untrusted input.

Fix: Confirm which files are written/deleted and that paths cannot be influenced by untrusted input.

MEDIUMPython network egressST-NET-PY

The component makes outbound network requests.

from urllib.parse import parse_qs, unquote_plus
token = parse_qs(self.path.partition("?")[2]).get("token", [""])[0]
if unquote_plus(sp.partition("?")[0]) == "SHUTDOWN THE SERVER":
import urllib.request
from urllib.parse import unquote
from urllib.request import url2pathname
decoded = unquote(name)
p = os.path.join(path_, url2pathname(resource_name))
pkg_dir = os.path.join(path_, url2pathname(pkg))
pkg_zip = os.path.join(path_, url2pathname(pkg + ".zip"))
p = os.path.join(path_, url2pathname(zipfile))
local_path = url2pathname(path_)
from urllib.error import HTTPError, URLError
response = requests.get(requests.compat.urljoin(self.url, "live"))
response = requests.get(requests.compat.urljoin(self.url, "ready"))
self.session = requests.Session()
import http.client
import urllib.request
from urllib.parse import unquote, urlparse
parsed = urlparse(raw)
raw = unquote(parsed.path)
parsed = urlparse(str(url_input))

Why it matters: Usually legitimate, but confirm the destinations are expected and no sensitive data leaves.

Fix: Confirm the destination hosts are expected and that no sensitive data is sent off-host.

Check your own component

Run the same evidence-backed scan on any MCP server, agent skill, or package.

Scan your own component

How we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →