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
58 lines
2.7 KiB
Python
58 lines
2.7 KiB
Python
"""Regression test for the pathsec sandbox bypass in
|
|
``nltk.parse.dependencygraph.DependencyGraph.load`` (CWE-22; CVE-2026-12867).
|
|
|
|
``load`` opened the caller-supplied filename with the builtin ``open``, which
|
|
bypasses NLTK's centralized file-access sentinel (``nltk.pathsec``). Where an
|
|
application enables the sandbox as a boundary and feeds it an attacker-influenced
|
|
filename, that allowed arbitrary local files to be read and their parsed content
|
|
returned via the graph's node words. ``load`` must instead route through
|
|
``pathsec.open`` so the read honours the allowed data roots.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from nltk import pathsec
|
|
from nltk.parse.dependencygraph import DependencyGraph
|
|
|
|
_RECORD = "1\tleaked-word\t_\tN\tN\t_\t0\troot\t_\t_\n\n"
|
|
|
|
|
|
class TestDependencyGraphLoadSandbox:
|
|
def test_load_enforces_pathsec_sandbox(self, tmp_path, monkeypatch):
|
|
"""Under ``ENFORCE``, an out-of-root path is rejected; in-root still loads."""
|
|
allowed = tmp_path / "data"
|
|
allowed.mkdir()
|
|
outside = tmp_path / "outside"
|
|
outside.mkdir()
|
|
monkeypatch.setattr(pathsec, "ENFORCE", True)
|
|
monkeypatch.setattr(pathsec, "_get_allowed_roots", lambda: {allowed.resolve()})
|
|
|
|
out_file = outside / "secret.conll"
|
|
out_file.write_text(_RECORD, encoding="utf-8")
|
|
# The global sandbox (no required_root) raises PermissionError for an
|
|
# out-of-root path; assert exactly that so the test can't pass on some
|
|
# unrelated error.
|
|
with pytest.raises(PermissionError):
|
|
DependencyGraph.load(str(out_file))
|
|
|
|
in_file = allowed / "ok.conll"
|
|
in_file.write_text(_RECORD, encoding="utf-8")
|
|
graphs = DependencyGraph.load(str(in_file))
|
|
words = [n["word"] for n in graphs[0].nodes.values() if n.get("word")]
|
|
assert words == ["leaked-word"]
|
|
|
|
def test_load_unchanged_when_sandbox_disabled(self, tmp_path, monkeypatch):
|
|
"""With the sandbox off, an out-of-root path still loads (only warns)."""
|
|
monkeypatch.setattr(pathsec, "ENFORCE", False)
|
|
# Force *no* allowed roots so the file is unambiguously out-of-root
|
|
# (tmp_path is usually inside tempfile.gettempdir(), which is allowed by
|
|
# default -- that would not exercise the disabled-sandbox path).
|
|
monkeypatch.setattr(pathsec, "_get_allowed_roots", set)
|
|
out_file = tmp_path / "anywhere.conll"
|
|
out_file.write_text(_RECORD, encoding="utf-8")
|
|
# Not enforcing: pathsec warns about the out-of-root path but still reads.
|
|
with pytest.warns(RuntimeWarning):
|
|
graphs = DependencyGraph.load(str(out_file))
|
|
words = [n["word"] for n in graphs[0].nodes.values() if n.get("word")]
|
|
assert words == ["leaked-word"]
|