SkillTotal

Is nltk safe?

No malicious indicators - review capabilities 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 6 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 20/100 (low).

nltk-3.10.3 3.10.3

python_package · pypi:nltk
LOW
20
/ 100 risk score
Snapshot · scanned Sep 24, 2026 · nltk-3.10.3@3.10.3 · engine 0.53.0 / ruleset 60

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
Unsafe deserialization
General Protections / Input Validation

Findings (6)

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.

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.

import urllib.request
from urllib.request import url2pathname
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_)
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
handlers.append(urllib.request.ProxyHandler(isolated_proxies))
handlers.append(urllib.request.ProxyBasicAuthHandler(handler.passwd))
handlers.append(urllib.request.ProxyDigestAuthHandler(handler.passwd))
if not proxied and not has_proxy_handler and urllib.request.getproxies():
handlers.append(urllib.request.ProxyHandler({}))
opener = urllib.request.build_opener(*handlers)
from urllib.request import (
    HTTPPasswordMgrWithDefaultRealm,
    ProxyBasicAuthHandler,
    ProxyDigestAuthHandler,
    ProxyHandler,
    build_opener,
    getproxies,
    install_opener,
)
proxy = getproxies()["http"]
proxy_handler = ProxyHandler({"https": proxy, "http": proxy})
opener = build_opener(proxy_handler)

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 →