Files
nltk--nltk/nltk/test/unit/test_senna_security.py
wehub-resource-sync 3454a55636
cffconvert / validate (push) Has been cancelled
ci-workflow / pre-commit (push) Has been cancelled
ci-workflow / Minimal NLTK Download Test (macos-latest) (push) Has been cancelled
ci-workflow / Minimal NLTK Download Test (ubuntu-latest) (push) Has been cancelled
ci-workflow / Minimal NLTK Download Test (windows-latest) (push) Has been cancelled
ci-workflow / Python 3.10 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.11 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.12 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.13 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.14 on macos-latest (push) Has been cancelled
ci-workflow / Python 3.14t on macos-latest (push) Has been cancelled
ci-workflow / Python 3.10 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.11 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.12 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.13 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.14 on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.14t on ubuntu-latest (push) Has been cancelled
ci-workflow / Python 3.10 on windows-latest (push) Has been cancelled
ci-workflow / Python 3.11 on windows-latest (push) Has been cancelled
ci-workflow / Python 3.12 on windows-latest (push) Has been cancelled
ci-workflow / Python 3.13 on windows-latest (push) Has been cancelled
ci-workflow / Python 3.14 on windows-latest (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:46:15 +08:00

94 lines
3.2 KiB
Python

"""Regression tests for the untrusted-search-path fix in the Senna wrapper.
``Senna.__init__`` accepted a *relative* ``senna_path`` (e.g. ".") and resolved
the executable against the current working directory. Because ``executable()``
returns a path that contains a separator (e.g. ``./senna-osx``),
``subprocess.Popen`` runs it directly from the CWD without consulting ``$PATH``,
so an attacker who can write a ``senna-<platform>`` file there would have it
executed -- running code from an untrusted location (CWE-829, an untrusted
search path, CWE-426/CWE-427).
Only an explicit *absolute* directory (or an absolute ``SENNA`` environment
variable) is now used; a relative ``senna_path`` no longer falls back to the CWD.
"""
import os
import pytest
from nltk.classify.senna import Senna
# Every platform-specific binary name executable() may pick.
_SENNA_BINARIES = (
"senna-linux64",
"senna-linux32",
"senna-win32.exe",
"senna-osx",
"senna",
)
def _plant_senna_binaries(directory):
"""Create an (executable) file for every candidate senna binary name."""
os.makedirs(directory, exist_ok=True)
for name in _SENNA_BINARIES:
target = os.path.join(directory, name)
with open(target, "wb"):
pass
os.chmod(target, 0o755)
return directory
def test_cwd_senna_binary_is_not_picked_up(tmp_path, monkeypatch):
"""A senna-<platform> planted in the CWD must not be auto-selected."""
_plant_senna_binaries(str(tmp_path)) # attacker-planted in the CWD
monkeypatch.chdir(tmp_path)
monkeypatch.delenv("SENNA", raising=False)
with pytest.raises(LookupError):
Senna(".", ["pos"])
def test_cwd_does_not_override_configured_senna(tmp_path, monkeypatch):
"""A CWD directory must not shadow an absolute SENNA environment variable."""
cwd = tmp_path / "cwd"
_plant_senna_binaries(str(cwd)) # attacker-planted in the CWD
trusted = tmp_path / "trusted"
_plant_senna_binaries(str(trusted)) # the configured (trusted) location
monkeypatch.chdir(cwd)
monkeypatch.setenv("SENNA", str(trusted))
senna = Senna(".", ["pos"])
assert os.path.realpath(senna._path) == os.path.realpath(
str(trusted)
), "CWD directory overrode the trusted SENNA location"
def test_relative_senna_env_is_rejected(tmp_path, monkeypatch):
"""A relative SENNA environment variable must not be resolved against CWD."""
_plant_senna_binaries(str(tmp_path)) # attacker-planted in the CWD
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("SENNA", ".")
with pytest.raises(LookupError):
Senna(".", ["pos"])
def test_absolute_path_still_accepted(tmp_path, monkeypatch):
"""An explicit absolute directory is still used as-is."""
abs_dir = _plant_senna_binaries(str(tmp_path / "senna"))
monkeypatch.delenv("SENNA", raising=False)
senna = Senna(abs_dir, ["pos"])
assert os.path.realpath(senna._path) == os.path.realpath(abs_dir)
def test_absolute_path_without_executable_raises(tmp_path, monkeypatch):
"""An absolute senna_path with no senna binary fails fast (not deferred)."""
empty = tmp_path / "empty"
empty.mkdir()
monkeypatch.delenv("SENNA", raising=False)
with pytest.raises(LookupError):
Senna(str(empty), ["pos"])