From f8d75f9abbdb106aff7e4956ea6324c6912687bc Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 4 Sep 2026 18:31:25 +0200 Subject: [PATCH 1/2] test: define packaging and bundled plugin behavior --- tests/test_packaging.py | 112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/test_packaging.py diff --git a/tests/test_packaging.py b/tests/test_packaging.py new file mode 100644 index 0000000..658d46a --- /dev/null +++ b/tests/test_packaging.py @@ -0,0 +1,112 @@ +"""Acceptance tests for the installable product (product track, first point). + +The shipped plugins travel inside the wheel, under ``coretrace_python/bundled``, and are +loaded by default: ``coretrace-python-analyzer --check app.py`` works right after +``pip install``. ``--plugins DIR`` adds directories on top of the bundle and +``--no-bundled-plugins`` drops it. The wheel carries the manifests, and the version has +one source of truth. + +Expected to remain red until ``engine.BUNDLED_PLUGINS`` and the CLI defaults exist. +""" + +from __future__ import annotations + +import subprocess +import sys +import zipfile +from importlib import metadata +from pathlib import Path + +import pytest + +import coretrace_python +from coretrace_python import engine +from coretrace_python.cli import main + +MISSING: Exception | None = None +if not hasattr(engine, "BUNDLED_PLUGINS"): + MISSING = AttributeError("engine has no BUNDLED_PLUGINS") + + +@pytest.fixture(autouse=True) +def require_packaging() -> None: + if MISSING is not None: + pytest.fail(f"packaging is not implemented yet: {MISSING}") + + +REPO = Path(__file__).resolve().parent.parent +EVAL = "def run(code):\n eval(code)\n" + + +def write_plugin(directory: Path) -> Path: + directory.mkdir(parents=True) + (directory / "plugin.toml").write_text( + 'name = "extra-hello"\nversion = "1.0.0"\nplugin_api = ">=1,<2"\nrequires = []\n' + 'provides = ["extra.hello"]\n\n[entrypoint]\nmodule = "hello"\nclass = "Hello"\n', + encoding="utf-8", + ) + (directory / "hello.py").write_text( + "from coretrace_python.findings import Confidence, Finding, Severity\n" + "from coretrace_python.plugins import Plugin\n" + "from coretrace_python.source import SourceSpan\n\n" + "class Hello(Plugin):\n name = 'extra-hello'\n\n" + " def analyze(self, ctx):\n" + " span = SourceSpan(ctx.module.span.source_id, 1, 1)\n" + " return [Finding('hello', 'hello', Severity.INFO, Confidence.HIGH, span)]\n", + encoding="utf-8", + ) + return directory + + +def test_bundled_plugins_live_inside_the_package() -> None: + bundled = engine.BUNDLED_PLUGINS + + assert bundled == Path(coretrace_python.__file__).resolve().parent / "bundled" + assert bundled.is_dir() + manifests = sorted(p.relative_to(bundled).as_posix() for p in bundled.rglob("plugin.toml")) + assert len(manifests) == 26 + assert "syntax/dangerous_eval/plugin.toml" in manifests + assert not (REPO / "plugins").exists() + + +def test_checks_use_the_bundle_by_default(tmp_path: Path, capsys) -> None: # type: ignore[no-untyped-def] + source = tmp_path / "app.py" + source.write_text(EVAL, encoding="utf-8") + + assert main(["--check", str(source)]) == 1 + assert "dangerous-eval" in capsys.readouterr().out + assert main(["--check", str(source), "--no-bundled-plugins"]) == 0 + assert capsys.readouterr().out == "no findings\ncoverage: 1/1 files, 1/1 functions\n" + + +def test_extra_plugin_directories_add_to_the_bundle(tmp_path: Path, capsys) -> None: # type: ignore[no-untyped-def] + source = tmp_path / "app.py" + source.write_text(EVAL, encoding="utf-8") + extra = write_plugin(tmp_path / "extra") + + assert main(["--check", str(source), "--plugins", str(extra)]) == 1 + output = capsys.readouterr().out + assert "dangerous-eval" in output and "hello" in output + assert main(["--check", str(source), "--plugins", str(extra), "--no-bundled-plugins", "--format", "json"]) == 1 + assert '"rule_id": "hello"' in capsys.readouterr().out + assert "dangerous-eval" not in capsys.readouterr().out + + +def test_the_version_has_one_source_of_truth() -> None: + assert metadata.version("coretrace-python-analyzer") == coretrace_python.__version__ + + +def test_the_wheel_carries_the_bundled_plugins(tmp_path: Path) -> None: + pytest.importorskip("build") + subprocess.run( + [sys.executable, "-m", "build", "--wheel", "--outdir", str(tmp_path), str(REPO)], + check=True, + capture_output=True, + ) + (wheel,) = tmp_path.glob("*.whl") + names = zipfile.ZipFile(wheel).namelist() + + assert "coretrace_python/bundled/syntax/dangerous_eval/plugin.toml" in names + assert "coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py" in names + assert sum(1 for n in names if n.endswith("/plugin.toml")) == 26 + assert not any(n.startswith("tests/") or "tests-project" in n for n in names) From dd303c2011a22fbd7eecf88a71509ac4471a6a6f Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 4 Sep 2026 18:31:25 +0200 Subject: [PATCH 2/2] feat: bundle the plugins in the package and add the publication workflow --- .github/workflows/ci.yml | 22 ++++++++++ .github/workflows/release.yml | 43 +++++++++++++++++++ README.md | 20 +++++++-- docs/architecture.md | 6 ++- pyproject.toml | 22 ++++++++++ .../dependency_policy/dependency_policy.py | 0 .../dependency/dependency_policy/plugin.toml | 0 .../reachable_vulnerability/plugin.toml | 0 .../reachable_vulnerability.py | 0 .../dependency/sample_advisories/plugin.toml | 0 .../sample_advisories/sample_advisories.py | 0 .../vulnerable_dependency/plugin.toml | 0 .../vulnerable_dependency.py | 0 .../bundled}/models/cli/cli_models.py | 0 .../bundled}/models/cli/plugin.toml | 0 .../models/credentials/credential_models.py | 0 .../bundled}/models/credentials/plugin.toml | 0 .../bundled}/models/django/django_models.py | 0 .../bundled}/models/django/plugin.toml | 0 .../bundled}/models/fastapi/fastapi_models.py | 0 .../bundled}/models/fastapi/plugin.toml | 0 .../bundled}/models/flask/flask_models.py | 0 .../bundled}/models/flask/plugin.toml | 0 .../models/http_clients/http_client_models.py | 0 .../bundled}/models/http_clients/plugin.toml | 0 .../bundled}/models/python_stdlib/plugin.toml | 0 .../models/python_stdlib/python_stdlib.py | 0 .../bundled}/models/sqlalchemy/plugin.toml | 0 .../models/sqlalchemy/sqlalchemy_models.py | 0 .../secrets/config_secrets/config_secrets.py | 0 .../secrets/config_secrets/plugin.toml | 0 .../hardcoded_secrets/hardcoded_secrets.py | 0 .../secrets/hardcoded_secrets/plugin.toml | 0 .../command_injection/command_injection.py | 0 .../security/command_injection/plugin.toml | 0 .../insecure_deserialization.py | 0 .../insecure_deserialization/plugin.toml | 0 .../security/open_redirect/open_redirect.py | 0 .../security/open_redirect/plugin.toml | 0 .../security/path_traversal/path_traversal.py | 0 .../security/path_traversal/plugin.toml | 0 .../plaintext_credentials.py | 0 .../plaintext_credentials/plugin.toml | 0 .../security/sql_injection/plugin.toml | 0 .../security/sql_injection/sql_injection.py | 0 .../bundled}/security/ssrf/plugin.toml | 0 .../bundled}/security/ssrf/ssrf.py | 0 .../bundled}/security/xss/plugin.toml | 0 .../bundled}/security/xss/xss.py | 0 .../syntax/dangerous_eval/dangerous_eval.py | 0 .../syntax/dangerous_eval/plugin.toml | 0 .../syntax/flask_debug/flask_debug.py | 0 .../bundled}/syntax/flask_debug/plugin.toml | 0 .../syntax/missing_timeout/missing_timeout.py | 0 .../syntax/missing_timeout/plugin.toml | 0 .../bundled}/syntax/weak_crypto/plugin.toml | 0 .../syntax/weak_crypto/weak_crypto.py | 0 src/coretrace_python/cli.py | 17 ++++++-- src/coretrace_python/engine.py | 11 ++++- tests/test_advisory_feeds.py | 2 +- tests/test_architecture.py | 2 + tests/test_audit_detectors.py | 2 +- tests/test_cache.py | 2 +- tests/test_cli.py | 4 +- tests/test_closures.py | 2 +- tests/test_config_secrets_coverage.py | 2 +- tests/test_correlation.py | 2 +- tests/test_dependencies.py | 2 +- tests/test_detectors.py | 2 +- tests/test_discovery.py | 8 ++-- tests/test_django_http_clients.py | 2 +- tests/test_django_routes.py | 2 +- tests/test_exceptions.py | 2 +- tests/test_factories_match_loop_else.py | 2 +- tests/test_fastapi_cli_precision.py | 2 +- tests/test_frameworks.py | 2 +- tests/test_heap.py | 2 +- tests/test_interprocedural_taint.py | 2 +- tests/test_match_patterns.py | 2 +- tests/test_parallel.py | 2 +- tests/test_plaintext_credentials.py | 2 +- tests/test_plugins.py | 2 +- tests/test_project.py | 2 +- tests/test_pyir_imports.py | 2 +- tests/test_real_code_precision.py | 2 +- tests/test_refutation.py | 2 +- tests/test_refutation_evidence.py | 2 +- tests/test_remaining_limits.py | 2 +- tests/test_secrets_sbom.py | 2 +- tests/test_self_state.py | 2 +- tests/test_syntax_control_flow.py | 2 +- tests/test_syntax_coverage.py | 2 +- tests/test_syntax_expressions.py | 2 +- 93 files changed, 170 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/release.yml rename {plugins => src/coretrace_python/bundled}/dependency/dependency_policy/dependency_policy.py (100%) rename {plugins => src/coretrace_python/bundled}/dependency/dependency_policy/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/dependency/reachable_vulnerability/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/dependency/reachable_vulnerability/reachable_vulnerability.py (100%) rename {plugins => src/coretrace_python/bundled}/dependency/sample_advisories/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/dependency/sample_advisories/sample_advisories.py (100%) rename {plugins => src/coretrace_python/bundled}/dependency/vulnerable_dependency/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/dependency/vulnerable_dependency/vulnerable_dependency.py (100%) rename {plugins => src/coretrace_python/bundled}/models/cli/cli_models.py (100%) rename {plugins => src/coretrace_python/bundled}/models/cli/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/models/credentials/credential_models.py (100%) rename {plugins => src/coretrace_python/bundled}/models/credentials/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/models/django/django_models.py (100%) rename {plugins => src/coretrace_python/bundled}/models/django/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/models/fastapi/fastapi_models.py (100%) rename {plugins => src/coretrace_python/bundled}/models/fastapi/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/models/flask/flask_models.py (100%) rename {plugins => src/coretrace_python/bundled}/models/flask/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/models/http_clients/http_client_models.py (100%) rename {plugins => src/coretrace_python/bundled}/models/http_clients/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/models/python_stdlib/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/models/python_stdlib/python_stdlib.py (100%) rename {plugins => src/coretrace_python/bundled}/models/sqlalchemy/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/models/sqlalchemy/sqlalchemy_models.py (100%) rename {plugins => src/coretrace_python/bundled}/secrets/config_secrets/config_secrets.py (100%) rename {plugins => src/coretrace_python/bundled}/secrets/config_secrets/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/secrets/hardcoded_secrets/hardcoded_secrets.py (100%) rename {plugins => src/coretrace_python/bundled}/secrets/hardcoded_secrets/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/command_injection/command_injection.py (100%) rename {plugins => src/coretrace_python/bundled}/security/command_injection/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/insecure_deserialization/insecure_deserialization.py (100%) rename {plugins => src/coretrace_python/bundled}/security/insecure_deserialization/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/open_redirect/open_redirect.py (100%) rename {plugins => src/coretrace_python/bundled}/security/open_redirect/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/path_traversal/path_traversal.py (100%) rename {plugins => src/coretrace_python/bundled}/security/path_traversal/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/plaintext_credentials/plaintext_credentials.py (100%) rename {plugins => src/coretrace_python/bundled}/security/plaintext_credentials/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/sql_injection/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/sql_injection/sql_injection.py (100%) rename {plugins => src/coretrace_python/bundled}/security/ssrf/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/ssrf/ssrf.py (100%) rename {plugins => src/coretrace_python/bundled}/security/xss/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/security/xss/xss.py (100%) rename {plugins => src/coretrace_python/bundled}/syntax/dangerous_eval/dangerous_eval.py (100%) rename {plugins => src/coretrace_python/bundled}/syntax/dangerous_eval/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/syntax/flask_debug/flask_debug.py (100%) rename {plugins => src/coretrace_python/bundled}/syntax/flask_debug/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/syntax/missing_timeout/missing_timeout.py (100%) rename {plugins => src/coretrace_python/bundled}/syntax/missing_timeout/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/syntax/weak_crypto/plugin.toml (100%) rename {plugins => src/coretrace_python/bundled}/syntax/weak_crypto/weak_crypto.py (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eeae8ab..da437e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,3 +19,25 @@ jobs: - run: python -m mypy - run: python -m ruff check . - run: python -m pytest + + package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: python -m pip install build twine + - run: python -m build + - run: python -m twine check dist/* + - name: Install the wheel in a clean environment and run a check + run: | + python -m venv /tmp/smoke + /tmp/smoke/bin/pip install dist/*.whl + printf 'def run(code):\n eval(code)\n' > /tmp/sample.py + /tmp/smoke/bin/coretrace-python-analyzer --check /tmp/sample.py --format json > /tmp/report.json || test $? -eq 1 + grep -q '"rule_id": "dangerous-eval"' /tmp/report.json + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fa52165 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,43 @@ +name: Release + +# Publishes the package to PyPI when a version tag is pushed (``v0.1.0``). The tag must +# match the version in pyproject.toml. Publication uses PyPI trusted publishing: the +# ``pypi`` environment and the trusted publisher for this repository and workflow must be +# configured on pypi.org once; no token is stored. + +on: + push: + tags: ["v*"] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Check that the tag matches the package version + run: | + version=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") + test "v$version" = "${GITHUB_REF_NAME}" + - run: python -m pip install build twine + - run: python -m build + - run: python -m twine check dist/* + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/README.md b/README.md index da2db84..6e864c0 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,23 @@ python -m pytest python -m ruff check . ``` -## Check a file with plugins +## Install ```bash -coretrace-python-analyzer --check app.py --plugins plugins/ -coretrace-python-analyzer --check src/ --plugins plugins/ --format sarif > report.sarif +pip install coretrace-python-analyzer +``` + +The package has no runtime dependency and ships its plugins: security models for the +standard library and the supported frameworks, the taint detectors, the secret scanners +and the dependency checks are loaded by default. `--plugins DIR` adds a directory of your +own plugins on top, `--no-bundled-plugins` runs without the shipped ones. + +## Check a file or a project + +```bash +coretrace-python-analyzer --check app.py +coretrace-python-analyzer --check src/ --format sarif > report.sarif +coretrace-python-analyzer --check src/ --plugins my_plugins/ --cache .coretrace --jobs 4 ``` Given a directory, every Python file below it is analysed as one project: modules are @@ -95,7 +107,7 @@ representation and derived results are dropped and only its semantic tables stay memory. `--plugins` is a directory searched recursively for `plugin.toml` manifests and may be -repeated. The repository ships a first set under `plugins/`: security models for the +repeated. The package ships its set under `coretrace_python/bundled/`: security models for the standard library, Flask, FastAPI, Django, SQLAlchemy, the Requests and httpx clients, and the click and Typer command-line frameworks (`models/`), taint detectors for SQL injection, command injection, path traversal, SSRF, XSS, insecure deserialization, open diff --git a/docs/architecture.md b/docs/architecture.md index d7f29b0..92ddd79 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1057,10 +1057,12 @@ Refuted # 25. Plugin Types -Possible organization: +Organization of the plugins shipped inside the package (`coretrace_python/bundled/`, +loaded by default; `--plugins DIR` adds external directories, `--no-bundled-plugins` +leaves them out): ```text -plugins/ +bundled/ ├── syntax/ │ ├── dangerous_eval/ │ └── weak_crypto/ diff --git a/pyproject.toml b/pyproject.toml index bd57050..74eab44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,13 +10,33 @@ readme = "README.md" requires-python = ">=3.11" license = { text = "Apache-2.0" } authors = [{ name = "CoreTrace" }] +keywords = ["static-analysis", "security", "taint", "sast", "python"] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Security", + "Topic :: Software Development :: Quality Assurance", +] dependencies = [] +[project.urls] +Homepage = "https://github.com/CoreTrace/coretrace-python-analyzer" +Repository = "https://github.com/CoreTrace/coretrace-python-analyzer" +Issues = "https://github.com/CoreTrace/coretrace-python-analyzer/issues" + [project.optional-dependencies] dev = [ + "build>=1.2", "mypy>=1.15", "pytest>=8.0", "ruff>=0.9", + "twine>=5.0", ] [project.scripts] @@ -40,3 +60,5 @@ known-first-party = ["coretrace_python"] python_version = "3.11" strict = true files = ["src"] +# Bundled plugins are loaded by path, not imported as modules. +exclude = ["src/coretrace_python/bundled/"] diff --git a/plugins/dependency/dependency_policy/dependency_policy.py b/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py similarity index 100% rename from plugins/dependency/dependency_policy/dependency_policy.py rename to src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py diff --git a/plugins/dependency/dependency_policy/plugin.toml b/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml similarity index 100% rename from plugins/dependency/dependency_policy/plugin.toml rename to src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml diff --git a/plugins/dependency/reachable_vulnerability/plugin.toml b/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml similarity index 100% rename from plugins/dependency/reachable_vulnerability/plugin.toml rename to src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml diff --git a/plugins/dependency/reachable_vulnerability/reachable_vulnerability.py b/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py similarity index 100% rename from plugins/dependency/reachable_vulnerability/reachable_vulnerability.py rename to src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py diff --git a/plugins/dependency/sample_advisories/plugin.toml b/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml similarity index 100% rename from plugins/dependency/sample_advisories/plugin.toml rename to src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml diff --git a/plugins/dependency/sample_advisories/sample_advisories.py b/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py similarity index 100% rename from plugins/dependency/sample_advisories/sample_advisories.py rename to src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py diff --git a/plugins/dependency/vulnerable_dependency/plugin.toml b/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml similarity index 100% rename from plugins/dependency/vulnerable_dependency/plugin.toml rename to src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml diff --git a/plugins/dependency/vulnerable_dependency/vulnerable_dependency.py b/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py similarity index 100% rename from plugins/dependency/vulnerable_dependency/vulnerable_dependency.py rename to src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py diff --git a/plugins/models/cli/cli_models.py b/src/coretrace_python/bundled/models/cli/cli_models.py similarity index 100% rename from plugins/models/cli/cli_models.py rename to src/coretrace_python/bundled/models/cli/cli_models.py diff --git a/plugins/models/cli/plugin.toml b/src/coretrace_python/bundled/models/cli/plugin.toml similarity index 100% rename from plugins/models/cli/plugin.toml rename to src/coretrace_python/bundled/models/cli/plugin.toml diff --git a/plugins/models/credentials/credential_models.py b/src/coretrace_python/bundled/models/credentials/credential_models.py similarity index 100% rename from plugins/models/credentials/credential_models.py rename to src/coretrace_python/bundled/models/credentials/credential_models.py diff --git a/plugins/models/credentials/plugin.toml b/src/coretrace_python/bundled/models/credentials/plugin.toml similarity index 100% rename from plugins/models/credentials/plugin.toml rename to src/coretrace_python/bundled/models/credentials/plugin.toml diff --git a/plugins/models/django/django_models.py b/src/coretrace_python/bundled/models/django/django_models.py similarity index 100% rename from plugins/models/django/django_models.py rename to src/coretrace_python/bundled/models/django/django_models.py diff --git a/plugins/models/django/plugin.toml b/src/coretrace_python/bundled/models/django/plugin.toml similarity index 100% rename from plugins/models/django/plugin.toml rename to src/coretrace_python/bundled/models/django/plugin.toml diff --git a/plugins/models/fastapi/fastapi_models.py b/src/coretrace_python/bundled/models/fastapi/fastapi_models.py similarity index 100% rename from plugins/models/fastapi/fastapi_models.py rename to src/coretrace_python/bundled/models/fastapi/fastapi_models.py diff --git a/plugins/models/fastapi/plugin.toml b/src/coretrace_python/bundled/models/fastapi/plugin.toml similarity index 100% rename from plugins/models/fastapi/plugin.toml rename to src/coretrace_python/bundled/models/fastapi/plugin.toml diff --git a/plugins/models/flask/flask_models.py b/src/coretrace_python/bundled/models/flask/flask_models.py similarity index 100% rename from plugins/models/flask/flask_models.py rename to src/coretrace_python/bundled/models/flask/flask_models.py diff --git a/plugins/models/flask/plugin.toml b/src/coretrace_python/bundled/models/flask/plugin.toml similarity index 100% rename from plugins/models/flask/plugin.toml rename to src/coretrace_python/bundled/models/flask/plugin.toml diff --git a/plugins/models/http_clients/http_client_models.py b/src/coretrace_python/bundled/models/http_clients/http_client_models.py similarity index 100% rename from plugins/models/http_clients/http_client_models.py rename to src/coretrace_python/bundled/models/http_clients/http_client_models.py diff --git a/plugins/models/http_clients/plugin.toml b/src/coretrace_python/bundled/models/http_clients/plugin.toml similarity index 100% rename from plugins/models/http_clients/plugin.toml rename to src/coretrace_python/bundled/models/http_clients/plugin.toml diff --git a/plugins/models/python_stdlib/plugin.toml b/src/coretrace_python/bundled/models/python_stdlib/plugin.toml similarity index 100% rename from plugins/models/python_stdlib/plugin.toml rename to src/coretrace_python/bundled/models/python_stdlib/plugin.toml diff --git a/plugins/models/python_stdlib/python_stdlib.py b/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py similarity index 100% rename from plugins/models/python_stdlib/python_stdlib.py rename to src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py diff --git a/plugins/models/sqlalchemy/plugin.toml b/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml similarity index 100% rename from plugins/models/sqlalchemy/plugin.toml rename to src/coretrace_python/bundled/models/sqlalchemy/plugin.toml diff --git a/plugins/models/sqlalchemy/sqlalchemy_models.py b/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py similarity index 100% rename from plugins/models/sqlalchemy/sqlalchemy_models.py rename to src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py diff --git a/plugins/secrets/config_secrets/config_secrets.py b/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py similarity index 100% rename from plugins/secrets/config_secrets/config_secrets.py rename to src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py diff --git a/plugins/secrets/config_secrets/plugin.toml b/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml similarity index 100% rename from plugins/secrets/config_secrets/plugin.toml rename to src/coretrace_python/bundled/secrets/config_secrets/plugin.toml diff --git a/plugins/secrets/hardcoded_secrets/hardcoded_secrets.py b/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py similarity index 100% rename from plugins/secrets/hardcoded_secrets/hardcoded_secrets.py rename to src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py diff --git a/plugins/secrets/hardcoded_secrets/plugin.toml b/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml similarity index 100% rename from plugins/secrets/hardcoded_secrets/plugin.toml rename to src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml diff --git a/plugins/security/command_injection/command_injection.py b/src/coretrace_python/bundled/security/command_injection/command_injection.py similarity index 100% rename from plugins/security/command_injection/command_injection.py rename to src/coretrace_python/bundled/security/command_injection/command_injection.py diff --git a/plugins/security/command_injection/plugin.toml b/src/coretrace_python/bundled/security/command_injection/plugin.toml similarity index 100% rename from plugins/security/command_injection/plugin.toml rename to src/coretrace_python/bundled/security/command_injection/plugin.toml diff --git a/plugins/security/insecure_deserialization/insecure_deserialization.py b/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py similarity index 100% rename from plugins/security/insecure_deserialization/insecure_deserialization.py rename to src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py diff --git a/plugins/security/insecure_deserialization/plugin.toml b/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml similarity index 100% rename from plugins/security/insecure_deserialization/plugin.toml rename to src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml diff --git a/plugins/security/open_redirect/open_redirect.py b/src/coretrace_python/bundled/security/open_redirect/open_redirect.py similarity index 100% rename from plugins/security/open_redirect/open_redirect.py rename to src/coretrace_python/bundled/security/open_redirect/open_redirect.py diff --git a/plugins/security/open_redirect/plugin.toml b/src/coretrace_python/bundled/security/open_redirect/plugin.toml similarity index 100% rename from plugins/security/open_redirect/plugin.toml rename to src/coretrace_python/bundled/security/open_redirect/plugin.toml diff --git a/plugins/security/path_traversal/path_traversal.py b/src/coretrace_python/bundled/security/path_traversal/path_traversal.py similarity index 100% rename from plugins/security/path_traversal/path_traversal.py rename to src/coretrace_python/bundled/security/path_traversal/path_traversal.py diff --git a/plugins/security/path_traversal/plugin.toml b/src/coretrace_python/bundled/security/path_traversal/plugin.toml similarity index 100% rename from plugins/security/path_traversal/plugin.toml rename to src/coretrace_python/bundled/security/path_traversal/plugin.toml diff --git a/plugins/security/plaintext_credentials/plaintext_credentials.py b/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py similarity index 100% rename from plugins/security/plaintext_credentials/plaintext_credentials.py rename to src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py diff --git a/plugins/security/plaintext_credentials/plugin.toml b/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml similarity index 100% rename from plugins/security/plaintext_credentials/plugin.toml rename to src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml diff --git a/plugins/security/sql_injection/plugin.toml b/src/coretrace_python/bundled/security/sql_injection/plugin.toml similarity index 100% rename from plugins/security/sql_injection/plugin.toml rename to src/coretrace_python/bundled/security/sql_injection/plugin.toml diff --git a/plugins/security/sql_injection/sql_injection.py b/src/coretrace_python/bundled/security/sql_injection/sql_injection.py similarity index 100% rename from plugins/security/sql_injection/sql_injection.py rename to src/coretrace_python/bundled/security/sql_injection/sql_injection.py diff --git a/plugins/security/ssrf/plugin.toml b/src/coretrace_python/bundled/security/ssrf/plugin.toml similarity index 100% rename from plugins/security/ssrf/plugin.toml rename to src/coretrace_python/bundled/security/ssrf/plugin.toml diff --git a/plugins/security/ssrf/ssrf.py b/src/coretrace_python/bundled/security/ssrf/ssrf.py similarity index 100% rename from plugins/security/ssrf/ssrf.py rename to src/coretrace_python/bundled/security/ssrf/ssrf.py diff --git a/plugins/security/xss/plugin.toml b/src/coretrace_python/bundled/security/xss/plugin.toml similarity index 100% rename from plugins/security/xss/plugin.toml rename to src/coretrace_python/bundled/security/xss/plugin.toml diff --git a/plugins/security/xss/xss.py b/src/coretrace_python/bundled/security/xss/xss.py similarity index 100% rename from plugins/security/xss/xss.py rename to src/coretrace_python/bundled/security/xss/xss.py diff --git a/plugins/syntax/dangerous_eval/dangerous_eval.py b/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py similarity index 100% rename from plugins/syntax/dangerous_eval/dangerous_eval.py rename to src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py diff --git a/plugins/syntax/dangerous_eval/plugin.toml b/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml similarity index 100% rename from plugins/syntax/dangerous_eval/plugin.toml rename to src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml diff --git a/plugins/syntax/flask_debug/flask_debug.py b/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py similarity index 100% rename from plugins/syntax/flask_debug/flask_debug.py rename to src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py diff --git a/plugins/syntax/flask_debug/plugin.toml b/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml similarity index 100% rename from plugins/syntax/flask_debug/plugin.toml rename to src/coretrace_python/bundled/syntax/flask_debug/plugin.toml diff --git a/plugins/syntax/missing_timeout/missing_timeout.py b/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py similarity index 100% rename from plugins/syntax/missing_timeout/missing_timeout.py rename to src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py diff --git a/plugins/syntax/missing_timeout/plugin.toml b/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml similarity index 100% rename from plugins/syntax/missing_timeout/plugin.toml rename to src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml diff --git a/plugins/syntax/weak_crypto/plugin.toml b/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml similarity index 100% rename from plugins/syntax/weak_crypto/plugin.toml rename to src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml diff --git a/plugins/syntax/weak_crypto/weak_crypto.py b/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py similarity index 100% rename from plugins/syntax/weak_crypto/weak_crypto.py rename to src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py diff --git a/src/coretrace_python/cli.py b/src/coretrace_python/cli.py index ff2d1cf..135a60a 100644 --- a/src/coretrace_python/cli.py +++ b/src/coretrace_python/cli.py @@ -67,7 +67,13 @@ def build_parser() -> argparse.ArgumentParser: type=Path, default=[], metavar="DIR", - help="with --check, a directory searched recursively for plugin.toml (repeatable)", + help="with --check, a directory searched recursively for plugin.toml, loaded on " + "top of the bundled plugins (repeatable)", + ) + parser.add_argument( + "--no-bundled-plugins", + action="store_true", + help="with --check, do not load the plugins shipped with the package", ) parser.add_argument( "--format", @@ -164,6 +170,11 @@ def main(argv: list[str] | None = None) -> int: print("error: --policy only applies to --check on a directory", file=sys.stderr) return EXIT_ERROR + if args.no_bundled_plugins and not args.check: + print("error: --no-bundled-plugins only applies to --check", file=sys.stderr) + return EXIT_ERROR + plugin_roots = ([] if args.no_bundled_plugins else [engine.BUNDLED_PLUGINS]) + list(args.plugins) + if args.path.is_dir() and args.emit_ir: print(f"error: {args.path} is a directory; --emit-ir needs a file", file=sys.stderr) return EXIT_ERROR @@ -177,7 +188,7 @@ def main(argv: list[str] | None = None) -> int: cache = None if args.cache is None else ProjectCache(args.cache) analysis = engine.analyze_project( args.path, - args.plugins, + plugin_roots, cache=cache, jobs=args.jobs or 1, advisory_files=args.advisories, @@ -191,7 +202,7 @@ def main(argv: list[str] | None = None) -> int: encoding="utf-8", ) else: - file_analysis = engine.analyze_file(SourceManager().load_file(args.path), args.plugins) + file_analysis = engine.analyze_file(SourceManager().load_file(args.path), plugin_roots) findings, coverage = file_analysis.findings, file_analysis.coverage print(render(args.format or "text", engine.report(findings, coverage)), end="") return EXIT_FINDINGS if findings else EXIT_CLEAN diff --git a/src/coretrace_python/engine.py b/src/coretrace_python/engine.py index 0593a23..73a5643 100644 --- a/src/coretrace_python/engine.py +++ b/src/coretrace_python/engine.py @@ -111,6 +111,10 @@ TOOL_NAME = "coretrace-python-analyzer" +# The plugins shipped with the package: models, detectors, secrets and dependency +# checks. They are loaded by default; ``--plugins`` adds directories on top. +BUNDLED_PLUGINS = Path(__file__).resolve().parent / "bundled" + ALL_ANALYSES: tuple[AnyAnalysis, ...] = ( *SEMANTIC_ANALYSES, CFGAnalysis, @@ -217,10 +221,15 @@ def _routes_of(manager: AnalysisManager) -> dict[SymbolId, EntryPoint]: def load_plugins(plugin_roots: Sequence[Path], manager: AnalysisManager) -> PluginRegistry: + """Every plugin below the roots, each manifest loaded once even when roots overlap.""" + registry = PluginRegistry() + seen: set[Path] = set() for root in plugin_roots: for loaded in discover_plugins(root, manager): - registry.add(loaded) + if loaded.directory.resolve() not in seen: + seen.add(loaded.directory.resolve()) + registry.add(loaded) return registry diff --git a/tests/test_advisory_feeds.py b/tests/test_advisory_feeds.py index 461660c..490014a 100644 --- a/tests/test_advisory_feeds.py +++ b/tests/test_advisory_feeds.py @@ -50,7 +50,7 @@ def require_feeds() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def osv(identifier: str, package: str, events: list[dict[str, str]], **extra: object) -> dict[str, object]: diff --git a/tests/test_architecture.py b/tests/test_architecture.py index 140c6d1..da42092 100644 --- a/tests/test_architecture.py +++ b/tests/test_architecture.py @@ -40,6 +40,8 @@ "dependency": 11, "plugins": 12, "reporters": 13, + # The plugins shipped with the package sit on top of the plugin API. + "bundled": 14, } # Top-level modules that wire the pipeline together and may import any layer. diff --git a/tests/test_audit_detectors.py b/tests/test_audit_detectors.py index 06a5f65..027bbe9 100644 --- a/tests/test_audit_detectors.py +++ b/tests/test_audit_detectors.py @@ -30,7 +30,7 @@ from coretrace_python.source import SourceManager REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" @pytest.fixture(autouse=True) diff --git a/tests/test_cache.py b/tests/test_cache.py index 1543f15..4612bf8 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -37,7 +37,7 @@ def require_cache() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" HELPERS = "import os\n\ndef execute(command):\n os.system(command)\n" MAIN = "from app.helpers import execute\n\ndef run():\n execute(input())\n" diff --git a/tests/test_cli.py b/tests/test_cli.py index 794c81d..0152d3f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -14,7 +14,7 @@ from coretrace_python.cli import main REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def write(tmp_path: Path, text: str, name: str = "target.py") -> Path: @@ -60,7 +60,7 @@ def test_check_is_clean_when_nothing_is_found(tmp_path, capsys) -> None: def test_check_without_plugins_finds_nothing(tmp_path, capsys) -> None: source = write(tmp_path, "def run(code):\n eval(code)\n") - assert main(["--check", str(source)]) == 0 + assert main(["--check", str(source), "--no-bundled-plugins"]) == 0 assert capsys.readouterr().out == "no findings\ncoverage: 1/1 files, 1/1 functions\n" diff --git a/tests/test_closures.py b/tests/test_closures.py index c82a141..23cd0e0 100644 --- a/tests/test_closures.py +++ b/tests/test_closures.py @@ -50,7 +50,7 @@ def require_closures() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def hir(text: str) -> nodes.Module: diff --git a/tests/test_config_secrets_coverage.py b/tests/test_config_secrets_coverage.py index 6929ba2..4cd4fe8 100644 --- a/tests/test_config_secrets_coverage.py +++ b/tests/test_config_secrets_coverage.py @@ -44,7 +44,7 @@ def require_pass() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" GITHUB_TOKEN = "ghp_" + "a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8" diff --git a/tests/test_correlation.py b/tests/test_correlation.py index 67848f8..f528b05 100644 --- a/tests/test_correlation.py +++ b/tests/test_correlation.py @@ -37,7 +37,7 @@ def require_correlation() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" VULNERABLE = "pyyaml==5.3.1\n" diff --git a/tests/test_dependencies.py b/tests/test_dependencies.py index 4153419..5fccb43 100644 --- a/tests/test_dependencies.py +++ b/tests/test_dependencies.py @@ -51,7 +51,7 @@ def require_dependencies() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def project(root: Path, files: dict[str, str]) -> Path: diff --git a/tests/test_detectors.py b/tests/test_detectors.py index cd1c126..9dea70a 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -47,7 +47,7 @@ def require_detectors() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def check(source_text: str, *extra_roots: Path, name: str = "app.py") -> tuple[Finding, ...]: diff --git a/tests/test_discovery.py b/tests/test_discovery.py index c702671..40a3632 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -64,7 +64,7 @@ def test_dashed_packages_are_analysed_instead_of_crashing(tmp_path: Path) -> Non }, ) - analysis = engine.analyze_project(root, [Path(__file__).resolve().parent.parent / "plugins"]) + analysis = engine.analyze_project(root, [Path(__file__).resolve().parent.parent / "src" / "coretrace_python" / "bundled"]) assert set(analysis.keys) == {"network-topologer", "network-topologer.traceroute", "network-topologer.__main__"} assert [(f.rule_id, Path(str(f.span.source_id)).name, f.span.start_line) for f in analysis.findings] == [ @@ -77,7 +77,7 @@ def test_undecodable_files_become_notes_and_the_rest_is_analysed(tmp_path: Path) root = project(tmp_path, {"app.py": "import os\n\ndef run():\n os.system(input())\n"}) (root / "legacy.py").write_bytes(b"print('x')\n\xff") - analysis = engine.analyze_project(root, [Path(__file__).resolve().parent.parent / "plugins"]) + analysis = engine.analyze_project(root, [Path(__file__).resolve().parent.parent / "src" / "coretrace_python" / "bundled"]) notes = [(f.rule_id, Path(str(f.span.source_id)).name) for f in analysis.findings] assert ("syntax-error", "legacy.py") in notes @@ -90,7 +90,7 @@ def test_utf16_dependency_files_are_read(tmp_path: Path) -> None: root = project(tmp_path, {"app.py": "x = 1\n"}) (root / "requirements.txt").write_bytes("pyyaml==5.3.1\r\nflask==2.0.0\r\n".encode("utf-16")) - analysis = engine.analyze_project(root, [Path(__file__).resolve().parent.parent / "plugins"]) + analysis = engine.analyze_project(root, [Path(__file__).resolve().parent.parent / "src" / "coretrace_python" / "bundled"]) assert analysis.dependencies.names == ("flask", "pyyaml") assert sorted(f.rule_id for f in analysis.findings) == ["vulnerable-dependency", "vulnerable-dependency"] @@ -114,5 +114,5 @@ def test_duplicate_qualified_names_get_distinct_call_graph_names() -> None: setter = module.body[1].body[1] # type: ignore[union-attr] assert graph.name_of(setter) == "Box.cmd__2" # type: ignore[arg-type] assert manager.get(TaintAnalysis, setter) is not None # type: ignore[arg-type] - findings = engine.check(source, [Path(__file__).resolve().parent.parent / "plugins"]) + findings = engine.check(source, [Path(__file__).resolve().parent.parent / "src" / "coretrace_python" / "bundled"]) assert [(f.rule_id, f.span.start_line) for f in findings] == [("command-injection", 11)] diff --git a/tests/test_django_http_clients.py b/tests/test_django_http_clients.py index fbb1227..97d2725 100644 --- a/tests/test_django_http_clients.py +++ b/tests/test_django_http_clients.py @@ -44,7 +44,7 @@ def require_models() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" OS_SYSTEM = Sink(SymbolId("python.os.system"), TaintKind.COMMAND) diff --git a/tests/test_django_routes.py b/tests/test_django_routes.py index 82e6ec1..76c1234 100644 --- a/tests/test_django_routes.py +++ b/tests/test_django_routes.py @@ -47,7 +47,7 @@ def require_routes() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def project(root: Path, files: dict[str, str]) -> Path: diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 3dd3a62..57f8689 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -27,7 +27,7 @@ from coretrace_python.source import SourceManager REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" @pytest.fixture(autouse=True) diff --git a/tests/test_factories_match_loop_else.py b/tests/test_factories_match_loop_else.py index 2e1b6db..965aa1a 100644 --- a/tests/test_factories_match_loop_else.py +++ b/tests/test_factories_match_loop_else.py @@ -42,7 +42,7 @@ def require_pass() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def check(text: str, name: str = "app.py") -> tuple[Finding, ...]: diff --git a/tests/test_fastapi_cli_precision.py b/tests/test_fastapi_cli_precision.py index 271ba74..28611ad 100644 --- a/tests/test_fastapi_cli_precision.py +++ b/tests/test_fastapi_cli_precision.py @@ -49,7 +49,7 @@ def require_pass() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def check(text: str, name: str = "app.py") -> tuple[Finding, ...]: diff --git a/tests/test_frameworks.py b/tests/test_frameworks.py index a62f862..a1afdac 100644 --- a/tests/test_frameworks.py +++ b/tests/test_frameworks.py @@ -54,7 +54,7 @@ def require_entry_points() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def module_for(source_text: str) -> nodes.Module: diff --git a/tests/test_heap.py b/tests/test_heap.py index ea8bf9c..8dfe789 100644 --- a/tests/test_heap.py +++ b/tests/test_heap.py @@ -64,7 +64,7 @@ def require_heap() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" MODELS = ( Source(SymbolId("python.builtins.input"), "stdin"), diff --git a/tests/test_interprocedural_taint.py b/tests/test_interprocedural_taint.py index 944c20f..4e1aaca 100644 --- a/tests/test_interprocedural_taint.py +++ b/tests/test_interprocedural_taint.py @@ -34,7 +34,7 @@ ) REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" MODELS = ( Source(SymbolId("python.builtins.input"), "stdin"), diff --git a/tests/test_match_patterns.py b/tests/test_match_patterns.py index f11579d..31bca8e 100644 --- a/tests/test_match_patterns.py +++ b/tests/test_match_patterns.py @@ -47,7 +47,7 @@ def require_patterns() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def hir(text: str) -> nodes.Module: diff --git a/tests/test_parallel.py b/tests/test_parallel.py index 057d247..96da7fe 100644 --- a/tests/test_parallel.py +++ b/tests/test_parallel.py @@ -52,7 +52,7 @@ def require_parallel() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" HELPERS = "import os\n\ndef execute(command):\n os.system(command)\n" MAIN = "from app.helpers import execute\n\ndef run():\n execute(input())\n" diff --git a/tests/test_plaintext_credentials.py b/tests/test_plaintext_credentials.py index efd771d..8609895 100644 --- a/tests/test_plaintext_credentials.py +++ b/tests/test_plaintext_credentials.py @@ -43,7 +43,7 @@ def require_credentials() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def check(text: str) -> tuple[Finding, ...]: diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 3bc564a..1bd2422 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -63,7 +63,7 @@ def require_plugin_api() -> None: REPO = Path(__file__).resolve().parent.parent -DANGEROUS_EVAL = REPO / "plugins" / "syntax" / "dangerous_eval" +DANGEROUS_EVAL = REPO / "src" / "coretrace_python" / "bundled" / "syntax" / "dangerous_eval" def manager_for(source_text: str) -> AnalysisManager: diff --git a/tests/test_project.py b/tests/test_project.py index 9c5b361..73cfa3d 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -42,7 +42,7 @@ def require_module_graph() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def project(root: Path, files: dict[str, str]) -> Path: diff --git a/tests/test_pyir_imports.py b/tests/test_pyir_imports.py index 3915c43..c3b64f3 100644 --- a/tests/test_pyir_imports.py +++ b/tests/test_pyir_imports.py @@ -40,7 +40,7 @@ def require_imports() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def lower(text: str, *, ssa: bool = False, name: str = "ir.py") -> FunctionIR: diff --git a/tests/test_real_code_precision.py b/tests/test_real_code_precision.py index 51db053..e551414 100644 --- a/tests/test_real_code_precision.py +++ b/tests/test_real_code_precision.py @@ -42,7 +42,7 @@ def require_precision() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def check(text: str) -> tuple[Finding, ...]: diff --git a/tests/test_refutation.py b/tests/test_refutation.py index 1c7f942..db18fe3 100644 --- a/tests/test_refutation.py +++ b/tests/test_refutation.py @@ -52,7 +52,7 @@ def require_refutation() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" MODELS = ( Source(SymbolId("python.builtins.input"), "stdin"), diff --git a/tests/test_refutation_evidence.py b/tests/test_refutation_evidence.py index 86a09f1..da6ec46 100644 --- a/tests/test_refutation_evidence.py +++ b/tests/test_refutation_evidence.py @@ -58,7 +58,7 @@ def require_evidence() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" OS_SYSTEM = Sink(SymbolId("python.os.system"), TaintKind.COMMAND) STDIN = Source(SymbolId("python.builtins.input"), "stdin") diff --git a/tests/test_remaining_limits.py b/tests/test_remaining_limits.py index d1198be..9a657f5 100644 --- a/tests/test_remaining_limits.py +++ b/tests/test_remaining_limits.py @@ -45,7 +45,7 @@ def require_limits() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def hir(text: str) -> nodes.Module: diff --git a/tests/test_secrets_sbom.py b/tests/test_secrets_sbom.py index 7c641c6..2c447ba 100644 --- a/tests/test_secrets_sbom.py +++ b/tests/test_secrets_sbom.py @@ -50,7 +50,7 @@ def require_secrets() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" AWS_KEY = "AKIAIOSFODNN7EXAMPLE" GITHUB_TOKEN = "ghp_" + "a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8" diff --git a/tests/test_self_state.py b/tests/test_self_state.py index 9bf245f..a1624c6 100644 --- a/tests/test_self_state.py +++ b/tests/test_self_state.py @@ -33,7 +33,7 @@ from coretrace_python.source import SourceManager REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" APP = ( "import os\nimport sys\n\n" diff --git a/tests/test_syntax_control_flow.py b/tests/test_syntax_control_flow.py index e3b9368..2c7d46d 100644 --- a/tests/test_syntax_control_flow.py +++ b/tests/test_syntax_control_flow.py @@ -55,7 +55,7 @@ def require_syntax() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" MODELS = ( Source(SymbolId("python.builtins.input"), "stdin"), diff --git a/tests/test_syntax_coverage.py b/tests/test_syntax_coverage.py index 630bf7f..22d423a 100644 --- a/tests/test_syntax_coverage.py +++ b/tests/test_syntax_coverage.py @@ -23,7 +23,7 @@ from coretrace_python.source import SourceManager REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" def build(source_text: str) -> nodes.Module: diff --git a/tests/test_syntax_expressions.py b/tests/test_syntax_expressions.py index 923c2a8..202c992 100644 --- a/tests/test_syntax_expressions.py +++ b/tests/test_syntax_expressions.py @@ -61,7 +61,7 @@ def require_syntax() -> None: REPO = Path(__file__).resolve().parent.parent -PLUGINS = REPO / "plugins" +PLUGINS = REPO / "src" / "coretrace_python" / "bundled" MODELS = ( Source(SymbolId("python.builtins.input"), "stdin"),