From b84a0c6b87ba5d3aa3da85fbec9ce9b284ed3e3d Mon Sep 17 00:00:00 2001 From: Amine Date: Mon, 21 Apr 2025 12:00:05 +0100 Subject: [PATCH 1/5] feat(security): Add package name typosquatting detection Implement typosquatting detection for package names during analysis. Compares package names against a list of popular packages using the Jaro-Winkler similarity algorithm. Packages exceeding a defined threshold of similarity to a popular package are flagged. Signed-off-by: Amine --- src/macaron/__main__.py | 11 + src/macaron/config/global_config.py | 5 + .../pypi_heuristics/heuristics.py | 3 + .../metadata/typosquatting_presence.py | 249 + src/macaron/resources/popular_packages.txt | 5000 +++++++++++++++++ .../checks/detect_malicious_metadata_check.py | 8 + 6 files changed, 5276 insertions(+) create mode 100644 src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py create mode 100644 src/macaron/resources/popular_packages.txt diff --git a/src/macaron/__main__.py b/src/macaron/__main__.py index 03549db7f..8fc3290e4 100644 --- a/src/macaron/__main__.py +++ b/src/macaron/__main__.py @@ -367,6 +367,16 @@ def main(argv: list[str] | None = None) -> None: help="The directory where Macaron looks for already cloned repositories.", ) + main_parser.add_argument( + "-pp", + "--popular-packages-path", + required=False, + type=str, + default=None, + help="The path to the popular packages file used for typosquatting detection.", + dest="popular_packages_path", + ) + # Add sub parsers for each action. sub_parser = main_parser.add_subparsers(dest="action", help="Run macaron --help for help") @@ -579,6 +589,7 @@ def main(argv: list[str] | None = None) -> None: build_log_path=os.path.join(args.output_dir, "build_log"), debug_level=log_level, local_repos_path=args.local_repos_path, + popular_packages_path=args.popular_packages_path, resources_path=os.path.join(macaron.MACARON_PATH, "resources"), ) diff --git a/src/macaron/config/global_config.py b/src/macaron/config/global_config.py index 8befb4045..805e9e7fa 100644 --- a/src/macaron/config/global_config.py +++ b/src/macaron/config/global_config.py @@ -49,6 +49,9 @@ class GlobalConfig: #: The path to the local .m2 Maven repository. This attribute is None if there is no available .m2 directory. local_maven_repo: str | None = None + #: The path to the popular packages file. + popular_packages_path: str | None = None + def load( self, macaron_path: str, @@ -57,6 +60,7 @@ def load( debug_level: int, local_repos_path: str, resources_path: str, + popular_packages_path: str, ) -> None: """Initiate the GlobalConfig object. @@ -81,6 +85,7 @@ def load( self.debug_level = debug_level self.local_repos_path = local_repos_path self.resources_path = resources_path + self.popular_packages_path = popular_packages_path def load_expectation_files(self, exp_path: str) -> None: """ diff --git a/src/macaron/malware_analyzer/pypi_heuristics/heuristics.py b/src/macaron/malware_analyzer/pypi_heuristics/heuristics.py index bd829a0f1..a5024b6ce 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/heuristics.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/heuristics.py @@ -37,6 +37,9 @@ class Heuristics(str, Enum): #: Indicates that the package has an unusually large version number for a single release. ANOMALOUS_VERSION = "anomalous_version" + #: Indicates that the package name is similar to a popular package. + TYPOSQUATTING_PRESENCE = "typosquatting_presence" + class HeuristicResult(str, Enum): """Result type indicating the outcome of a heuristic.""" diff --git a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py new file mode 100644 index 000000000..71ea90988 --- /dev/null +++ b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py @@ -0,0 +1,249 @@ +# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. + +"""Analyzer checks if there is typosquatting presence in the package name.""" +import logging +import os + +from macaron.config.global_config import global_config +from macaron.json_tools import JsonType +from macaron.malware_analyzer.pypi_heuristics.base_analyzer import BaseHeuristicAnalyzer +from macaron.malware_analyzer.pypi_heuristics.heuristics import HeuristicResult, Heuristics +from macaron.slsa_analyzer.package_registry.pypi_registry import PyPIPackageJsonAsset + +logger = logging.getLogger(__name__) + + +class TyposquattingPresenceAnalyzer(BaseHeuristicAnalyzer): + """Check whether the PyPI package has typosquatting presence.""" + + def __init__(self) -> None: + super().__init__( + name="typosquatting_presence_analyzer", heuristic=Heuristics.TYPOSQUATTING_PRESENCE, depends_on=None + ) + self.popular_packages_path = os.path.join(global_config.resources_path, "popular_packages.txt") + self.distance_ratio_threshold = 0.95 + self.cost = 1 + self.scaling = 0.15 + self.keyboard = 0.8 + self.keyboard_layout = { + "1": (-1, 0), + "2": (-1, 1), + "3": (-1, 2), + "4": (-1, 3), + "5": (-1, 4), + "6": (-1, 5), + "7": (-1, 6), + "8": (-1, 7), + "9": (-1, 8), + "0": (-1, 9), + "-": (-1, 10), + "q": (0, 0), + "w": (0, 1), + "e": (0, 2), + "r": (0, 3), + "t": (0, 4), + "y": (0, 5), + "u": (0, 6), + "i": (0, 7), + "o": (0, 8), + "p": (0, 9), + "a": (1, 0), + "s": (1, 1), + "d": (1, 2), + "f": (1, 3), + "g": (1, 4), + "h": (1, 5), + "j": (1, 6), + "k": (1, 7), + "l": (1, 8), + "z": (2, 0), + "x": (2, 1), + "c": (2, 2), + "v": (2, 3), + "b": (2, 4), + "n": (2, 5), + "m": (2, 6), + } + + if global_config.popular_packages_path is not None: + self.popular_packages_path = global_config.popular_packages_path + + def are_neighbors(self, char1: str, char2: str) -> bool: + """Check if two characters are adjacent on a QWERTY keyboard. + + Parameters + ---------- + char1 : str + The first character. + char2 : str + The second character. + + Returns + ------- + bool + True if the characters are neighbors, False otherwise. + """ + c1 = self.keyboard_layout.get(char1) + c2 = self.keyboard_layout.get(char2) + if not c1 or not c2: + return False + return (abs(c1[0] - c2[0]) <= 1) and (abs(c1[1] - c2[1]) <= 1) + + def substitution_func(self, char1: str, char2: str) -> float: + """Calculate the substitution cost between two characters. + + Parameters + ---------- + char1 : str + The first character. + char2 : str + The second character. + + Returns + ------- + float + 0.0 if the characters are the same, `self.keyboard` if they are + neighbors on a QWERTY keyboard, and `self.cost` otherwise. + """ + if char1 == char2: + return 0.0 + if self.keyboard and self.are_neighbors(char1, char2): + return self.keyboard + return self.cost + + def jaro_distance(self, package_name: str, popular_package_name: str) -> float: + """Calculate the Jaro distance between two package names. + + Parameters + ---------- + package_name : str + The name of the package being analyzed. + popular_package_name : str + The name of a popular package to compare against. + + Returns + ------- + float + The Jaro distance between the two package names. + """ + if package_name == popular_package_name: + return 1.0 + + len1, len2 = len(package_name), len(popular_package_name) + if len1 == 0 or len2 == 0: + return 0.0 + + match_distance = max(len1, len2) // 2 - 1 + + package_name_matches = [False] * len1 + popular_package_name_matches = [False] * len2 + matches = 0 + transpositions = 0.0 # Now a float to handle partial costs + + # Count matches + for i in range(len1): + start = max(0, i - match_distance) + end = min(i + match_distance + 1, len2) + for j in range(start, end): + if popular_package_name_matches[j]: + continue + if package_name[i] == popular_package_name[j]: + package_name_matches[i] = True + popular_package_name_matches[j] = True + matches += 1 + break + + if matches == 0: + return 0.0 + + # Count transpositions with possible keyboard awareness + k = 0 + for i in range(len1): + if package_name_matches[i]: + while not popular_package_name_matches[k]: + k += 1 + if package_name[i] != popular_package_name[k]: + transpositions += self.substitution_func(package_name[i], popular_package_name[k]) + k += 1 + + transpositions /= 2.0 # Adjust for transpositions being counted twice + + return (matches / len1 + matches / len2 + (matches - transpositions) / matches) / 3.0 + + def ratio(self, package_name: str, popular_package_name: str) -> float: + """Calculate the Jaro-Winkler distance ratio. + + Parameters + ---------- + package_name : str + The name of the package being analyzed. + popular_package_name : str + The name of a popular package to compare against. + + Returns + ------- + float + The Jaro-Winkler distance ratio, incorporating a prefix bonus + for common initial characters. + """ + scaling = self.scaling + jaro_dist = self.jaro_distance(package_name, popular_package_name) + prefix_length = 0 + max_prefix = 4 + for i in range(min(max_prefix, len(package_name), len(popular_package_name))): + if package_name[i] == popular_package_name[i]: + prefix_length += 1 + else: + break + + return jaro_dist + prefix_length * scaling * (1 - jaro_dist) + + def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicResult, dict[str, JsonType]]: + """Analyze the package. + + Parameters + ---------- + pypi_package_json: PyPIPackageJsonAsset + The PyPI package JSON asset object. + + Returns + ------- + tuple[HeuristicResult, dict[str, JsonType]]: + The result and related information collected during the analysis. + """ + # If there is a popular packages file, check if the package name is similar to any of them + package_name = pypi_package_json.component_name + if not self.popular_packages_path or not os.path.exists(self.popular_packages_path): + err_msg = f"Popular packages file not found or path not configured: {self.popular_packages_path}" + logger.warning("%s. Skipping typosquatting check.", err_msg) + return HeuristicResult.SKIP, {"error": err_msg} + + popular_packages = [] + try: + with open(self.popular_packages_path, encoding="utf-8") as file: + popular_packages = file.read().splitlines() + except OSError as e: + err_msg = f"Could not read popular packages file {self.popular_packages_path}: {e}" + logger.error(err_msg) + return HeuristicResult.SKIP, {"error": err_msg} + + for popular_package in popular_packages: + if package_name == popular_package: + return HeuristicResult.PASS, {"package_name": package_name} + + distance_ratio = self.ratio(package_name, popular_package) + if distance_ratio >= self.distance_ratio_threshold: + logger.info( + "Potential typosquatting detected: '%s' is similar to popular package '%s' (ratio: %.3f)", + package_name, + popular_package, + distance_ratio, + ) + return HeuristicResult.FAIL, { + "package_name": package_name, + "popular_package": popular_package, + "similarity_ratio": distance_ratio, + } + + return HeuristicResult.PASS, {"package_name": package_name} diff --git a/src/macaron/resources/popular_packages.txt b/src/macaron/resources/popular_packages.txt new file mode 100644 index 000000000..02da7afeb --- /dev/null +++ b/src/macaron/resources/popular_packages.txt @@ -0,0 +1,5000 @@ +boto3 +urllib3 +setuptools +botocore +requests +certifi +aiobotocore +charset-normalizer +idna +typing-extensions +packaging +grpcio-status +python-dateutil +s3transfer +s3fs +six +numpy +pyyaml +fsspec +google-api-core +cryptography +pip +pydantic +attrs +cffi +pandas +pycparser +protobuf +markupsafe +jinja2 +rsa +importlib-metadata +pyasn1 +jmespath +aiohttp +platformdirs +click +wheel +pytz +zipp +colorama +pydantic-core +googleapis-common-protos +awscli +cachetools +filelock +pluggy +virtualenv +google-auth +wrapt +tzdata +pyjwt +pyasn1-modules +pytest +jsonschema +tomli +annotated-types +sniffio +pygments +anyio +h11 +iniconfig +psutil +pyarrow +rich +httpx +sqlalchemy +requests-oauthlib +multidict +yarl +grpcio +pyparsing +httpcore +tomlkit +frozenlist +docutils +oauthlib +pathspec +aiosignal +requests-toolbelt +tqdm +pillow +beautifulsoup4 +distlib +werkzeug +google-cloud-storage +more-itertools +exceptiongroup +scipy +greenlet +soupsieve +pyopenssl +deprecated +propcache +rpds-py +et-xmlfile +openpyxl +isodate +trove-classifiers +referencing +decorator +lxml +async-timeout +jsonschema-specifications +python-dotenv +aiohappyeyeballs +proto-plus +msgpack +grpcio-tools +poetry-core +markdown-it-py +google-cloud-core +sortedcontainers +pynacl +mypy-extensions +flask +coverage +websocket-client +mdurl +gitpython +shellingham +azure-core +psycopg2-binary +tenacity +google-resumable-media +asn1crypto +itsdangerous +opentelemetry-api +smmap +regex +bcrypt +gitdb +msal +langsmith +scikit-learn +google-crc32c +dill +keyring +ptyprocess +wcwidth +chardet +pexpect +matplotlib +paramiko +snowflake-connector-python +pyproject-hooks +tabulate +alembic +jeepney +blinker +build +opentelemetry-semantic-conventions +jaraco-classes +opentelemetry-sdk +fastjsonschema +rapidfuzz +networkx +importlib-resources +huggingface-hub +cloudpickle +starlette +fastapi +secretstorage +threadpoolctl +typedload +google-cloud-bigquery +backoff +ruamel-yaml +joblib +kiwisolver +google-api-python-client +sqlparse +fonttools +asgiref +dnspython +pkginfo +prompt-toolkit +cycler +google-auth-oauthlib +py4j +httplib2 +defusedxml +pyzmq +xmltodict +uritemplate +poetry-plugin-export +types-requests +docker +uvicorn +cachecontrol +grpc-google-iam-v1 +google-auth-httplib2 +pytest-cov +azure-identity +gunicorn +marshmallow +awswrangler +installer +azure-storage-blob +msal-extensions +babel +cython +langchain +openai +poetry +redis +distro +tzlocal +contourpy +ruamel-yaml-clib +ipython +toml +setuptools-scm +dulwich +isort +crashtest +black +nest-asyncio +mccabe +jaraco-functools +pycodestyle +traitlets +cleo +jaraco-context +transformers +hatchling +jedi +opentelemetry-proto +py +zstandard +jsonpointer +pymysql +parso +websockets +typer +sentry-sdk +tornado +prometheus-client +markdown +matplotlib-inline +kubernetes +mako +webencodings +pendulum +opentelemetry-exporter-otlp-proto-common +termcolor +types-python-dateutil +python-json-logger +asttokens +executing +orjson +mypy +tokenizers +ruff +pyrsistent +opentelemetry-exporter-otlp-proto-http +aiofiles +stack-data +pure-eval +typing-inspect +arrow +ply +multiprocess +sympy +nodeenv +pycryptodome +future +argcomplete +torch +pymongo +rich-toolkit +opentelemetry-exporter-otlp-proto-grpc +shapely +pygithub +google-cloud-pubsub +smart-open +datadog +google-cloud-secret-manager +scramp +debugpy +snowflake-sqlalchemy +pytest-xdist +pycryptodomex +mpmath +opentelemetry-instrumentation +mysql-connector-python +aioitertools +python-slugify +identify +portalocker +jsonpatch +lz4 +pyflakes +pre-commit +backports-tarfile +cfgv +jupyter-core +jiter +jupyter-client +pyspark +requests-aws4auth +setproctitle +watchdog +slack-sdk +ipykernel +execnet +requests-file +opensearch-py +msrest +jupyterlab +croniter +langchain-core +thriftpy2 +rfc3339-validator +semver +comm +flake8 +jsonpath-ng +azure-common +opentelemetry-util-http +tinycss2 +bleach +redshift-connector +pytest-asyncio +mistune +cattrs +typeguard +tiktoken +nbformat +nbconvert +colorlog +opentelemetry-exporter-otlp +notebook +pytest-mock +elasticsearch +text-unidecode +google-cloud-aiplatform +nbclient +zope-interface +uv +pytzdata +xlsxwriter +jupyter-server +google-cloud-resource-manager +db-dtypes +editables +safetensors +google-cloud-appengine-logging +dataclasses-json +toolz +tb-nightly +pandas-gbq +overrides +pylint +nltk +pg8000 +gcsfs +astroid +argon2-cffi +pydantic-settings +email-validator +databricks-sql-connector +humanfriendly +google-pasta +argon2-cffi-bindings +python-multipart +pbs-installer +sphinx +findpython +lazy-object-proxy +pysocks +types-pyyaml +xlrd +docstring-parser +altair +ordered-set +deepdiff +pandocfilters +jupyterlab-server +jupyterlab-pygments +simplejson +absl-py +nvidia-cublas-cu12 +json5 +retry +wsproto +opentelemetry-instrumentation-requests +seaborn +google-cloud-logging +selenium +nvidia-cusparse-cu12 +apache-airflow-providers-common-sql +pkgutil-resolve-name +imageio +time-machine +structlog +nvidia-nvjitlink-cu12 +tblib +nvidia-cudnn-cu12 +watchfiles +nvidia-cufft-cu12 +nvidia-cuda-cupti-cu12 +uvloop +aenum +durationpy +nvidia-cuda-nvrtc-cu12 +nvidia-curand-cu12 +webcolors +nvidia-cusolver-cu12 +nvidia-cuda-runtime-cu12 +oscrypto +pydata-google-auth +xgboost +responses +send2trash +tensorboard +faker +mdit-py-plugins +schema +pbr +httptools +google-cloud-audit-log +graphql-core +google-cloud-dataproc +ipywidgets +fqdn +terminado +isoduration +nvidia-nccl-cu12 +uri-template +async-lru +numba +sagemaker +sentencepiece +widgetsnbextension +flatbuffers +rfc3986-validator +dbt-core +trio +jupyterlab-widgets +notebook-shim +jupyter-events +datasets +appdirs +llvmlite +narwhals +sshtunnel +pyodbc +databricks-sdk +libcst +plotly +progressbar2 +mock +triton +dacite +coloredlogs +inflection +google-cloud-vision +xxhash +jupyter-server-terminals +h5py +thrift +azure-storage-file-datalake +deprecation +aws-lambda-powertools +tensorflow +flask-caching +django +confluent-kafka +oauth2client +outcome +google-cloud-spanner +rfc3986 +python-utils +fastavro +semantic-version +jupyter-lsp +dbt-adapters +linkify-it-py +types-protobuf +azure-keyvault-secrets +pipenv +hpack +google-cloud-bigquery-storage +pathos +python-daemon +pymssql +adal +google-cloud-dlp +great-expectations +delta-spark +antlr4-python3-runtime +hyperframe +google-cloud-monitoring +looker-sdk +fastapi-cli +pox +apache-airflow-providers-snowflake +h2 +gym-notices +ppft +types-pytz +retrying +loguru +google-cloud-kms +tox +html5lib +trio-websocket +apache-airflow +nvidia-nvtx-cu12 +langchain-community +grpcio-health-checking +google-cloud-bigtable +flask-wtf +google-cloud-tasks +click-plugins +gast +omegaconf +apache-airflow-providers-mysql +psycopg2 +zeep +apache-airflow-providers-ssh +snowballstemmer +smdebug-rulesconfig +docker-pycreds +apache-airflow-providers-cncf-kubernetes +google-ads +prettytable +sqlalchemy-bigquery +aws-requests-auth +apache-airflow-providers-google +tldextract +kombu +google-cloud-datacatalog +opencv-python +boto3-stubs +google-cloud-bigquery-datatransfer +google-cloud-container +universal-pathlib +wandb +types-setuptools +brotli +botocore-stubs +freezegun +unidecode +google-cloud-translate +apache-airflow-providers-databricks +google-cloud-firestore +yamllint +lockfile +twine +torchvision +google-cloud-language +google-cloud-videointelligence +google-cloud-workflows +hvac +entrypoints +amqp +google-cloud-redis +google-cloud-build +google-cloud-automl +hypothesis +types-awscrt +google-cloud-dataplex +statsmodels +gcloud-aio-storage +gcloud-aio-auth +vine +gspread +google-cloud-os-login +google-cloud-speech +google-cloud-memcache +authlib +pybind11 +google-cloud-orchestration-airflow +google-cloud-compute +langchain-text-splitters +rich-argparse +bs4 +google-cloud-dataproc-metastore +types-s3transfer +google-cloud-dataform +flask-login +yapf +mashumaro +cached-property +kafka-python +asynctest +ujson +aiosqlite +agate +mergedeep +pytimeparse +google-cloud-texttospeech +types-urllib3 +celery +gcloud-aio-bigquery +gevent +azure-mgmt-core +grpcio-gcp +pytest-rerunfailures +monotonic +patsy +alabaster +pycountry +marshmallow-oneofschema +argparse +billiard +pytest-metadata +sphinxcontrib-serializinghtml +ecdsa +astronomer-cosmos +graphviz +ninja +gremlinpython +imagesize +nh3 +pip-tools +moto +mlflow +sphinxcontrib-htmlhelp +sphinxcontrib-applehelp +sphinxcontrib-qthelp +ijson +sphinxcontrib-devhelp +spacy +tensorboard-data-server +flask-cors +pydeequ +pywin32 +humanize +flit-core +psycopg +pickleshare +simple-salesforce +click-didyoumean +imbalanced-learn +opt-einsum +keras +flask-session +requests-mock +scikit-image +readme-renderer +ddtrace +backcall +mlflow-skinny +mysqlclient +sphinxcontrib-jsmath +makefun +zope-event +onnxruntime +docopt +parsedatetime +click-repl +databricks-cli +duckdb +dbt-common +blis +tensorflow-estimator +mypy-boto3-s3 +graphene +thinc +graphql-relay +opentelemetry-instrumentation-asgi +sqlalchemy-jsonfield +google-cloud-dataflow-client +bytecode +mmh3 +pytest-timeout +preshed +catalogue +avro-python3 +srsly +ipython-pygments-lexers +configupdater +envier +wasabi +opentelemetry-instrumentation-fastapi +langcodes +cron-descriptor +google-cloud-run +python-gitlab +jsonpickle +cymem +python-jose +murmurhash +leather +langchain-google-vertexai +commonmark +texttable +emoji +bitarray +cramjam +pypdf +langchain-openai +jira +dbt-extractor +spacy-legacy +ml-dtypes +google-cloud-storage-transfer +astunparse +spacy-loggers +pypdf2 +apache-beam +lark +pytest-runner +sqlalchemy-spanner +typing-inspection +confection +dbt-semantic-interfaces +google-cloud-batch +jpype1 +dask +python-http-client +pyproject-api +flask-sqlalchemy +uc-micro-py +asyncpg +py-cpuinfo +msrestazure +levenshtein +python-magic +polars +sendgrid +events +sqlalchemy-utils +anthropic +diskcache +accelerate +marisa-trie +elastic-transport +phonenumbers +djangorestframework +pytest-html +python-gnupg +azure-datalake-store +stevedore +cloudpathlib +types-redis +dateparser +apispec +pysftp +pyroaring +watchtower +opencv-python-headless +nvidia-cusparselt-cu12 +cssselect +astor +fasteners +libclang +azure-cosmos +rich-click +holidays +language-data +configparser +grpc-interceptor +azure-mgmt-resource +cmake +validators +aws-xray-sdk +wtforms +lightgbm +eval-type-backport +gym +httpx-sse +psycopg-binary +datetime +pyproj +parameterized +hyperlink +datadog-api-client +azure-storage-queue +avro +pyee +jaydebeapi +bracex +tomli-w +opencensus +azure-servicebus +einops +inflect +cfn-lint +invoke +opencensus-context +resolvelib +streamlit +pyathena +office365-rest-python-client +dataclasses +apache-airflow-providers-http +parse +types-paramiko +jupyter-console +flask-appbuilder +torchaudio +jupyter +tensorflow-io-gcs-filesystem +passlib +playwright +filetype +microsoft-kiota-http +lazy-loader +pydub +mypy-boto3-rds +markdownify +retryhttp +pydot +id +jax +tifffile +cachelib +pymdown-extensions +fuzzywuzzy +gradio +torchmetrics +sqlglot +google-analytics-admin +apscheduler +limits +posthog +tensorflow-serving-api +backports-zoneinfo +python-docx +azure-storage-file-share +microsoft-kiota-authentication-azure +snowflake-snowpark-python +openapi-spec-validator +sentence-transformers +ua-parser +types-tabulate +google-re2 +junitparser +pymupdf +ratelimit +pyright +connexion +oracledb +userpath +django-cors-headers +wcmatch +azure-kusto-data +junit-xml +gql +pytorch-lightning +configargparse +click-option-group +pyspnego +apache-airflow-providers-sqlite +types-deprecated +pydeck +pdfminer-six +fire +cfn-flip +pycrypto +fastparquet +azure-mgmt-storage +nose +pyotp +json-merge-patch +boto +xarray +lightning-utilities +iso8601 +contextlib2 +google +tableauserverclient +kfp +pywavelets +python-levenshtein +apache-airflow-providers-slack +starkbank-ecdsa +prefect +opentelemetry-instrumentation-wsgi +pipdeptree +kubernetes-asyncio +pandas-stubs +cssselect2 +timm +partd +locket +jsonref +aioresponses +aiohttp-retry +mkdocs-material +slicer +azure-keyvault-keys +lbprodrun +stripe +azure-nspkg +pandera +pytest-random-order +geographiclib +opencensus-ext-azure +apache-airflow-providers-ftp +ray +geopy +yandexcloud +shap +pyserial +pep517 +netaddr +factory-boy +textual +typing +types-aiofiles +geopandas +aniso8601 +checkov +jellyfish +weasel +msgspec +azure-storage-common +jsonlines +keras-applications +frozendict +tensorflow-text +openapi-schema-validator +natsort +iso3166 +orderly-set +flask-jwt-extended +ddsketch +aws-sam-translator +marshmallow-sqlalchemy +ansible-core +types-docutils +h3 +faiss-cpu +pycares +types-markdown +albumentations +aiodns +weaviate-client +boltons +reportlab +service-identity +jsondiff +ldap3 +enum34 +flask-limiter +onnx +slackclient +incremental +ansible +litellm +aliyun-python-sdk-core +querystring-parser +maxminddb +mkdocs +ciso8601 +meson +cloudevents +ipython-genutils +jaxlib +autopep8 +types-dataclasses +azure-keyvault +sql-metadata +django-filter +scp +azure-keyvault-certificates +protobuf3-to-dict +bandit +deltalake +keyrings-google-artifactregistry-auth +diracx-core +magicattr +immutabledict +types-croniter +opentelemetry-instrumentation-flask +binaryornot +sphinx-rtd-theme +twisted +ftfy +apache-airflow-providers-fab +python-nvd3 +ghp-import +opentelemetry-instrumentation-urllib3 +pytest-env +analytics-python +oldest-supported-numpy +hiredis +pyyaml-env-tag +strictyaml +pywin32-ctypes +bottle +langdetect +pika +pytest-localserver +opentelemetry-instrumentation-dbapi +logbook +twilio +azure-mgmt-containerregistry +asyncio +apache-airflow-providers-imap +statsd +sh +geoip2 +tree-sitter +llama-parse +typed-ast +pathy +flask-babel +ultralytics +rdflib +beartype +pydash +opentelemetry-instrumentation-urllib +cloudformation-cli +types-pymysql +gradio-client +cloudformation-cli-python-plugin +awscrt +mypy-boto3-sqs +methodtools +azure-mgmt-datafactory +diff-cover +cloudformation-cli-java-plugin +cloudformation-cli-go-plugin +cloudformation-cli-typescript-plugin +optree +firebase-admin +webdriver-manager +crcmod +mypy-boto3-glue +azure-eventhub +pyperclip +soundfile +requests-ntlm +aioboto3 +apache-airflow-providers-docker +minimal-snowplow-tracker +google-cloud +av +pytest-django +daff +fabric +mkdocs-material-extensions +clickhouse-connect +constructs +bidict +trino +pydantic-extra-types +mkdocstrings-python +unicodecsv +wirerope +marshmallow-enum +meson-python +automat +sagemaker-core +applicationinsights +pyproject-metadata +constantly +opentelemetry-instrumentation-psycopg2 +waitress +functions-framework +dash +chroma-hnswlib +cookiecutter +fs +ipdb +strenum +cligj +qrcode +opentelemetry-instrumentation-django +azure-mgmt-cosmosdb +pytest-forked +pyrfc3339 +namex +azure-mgmt-compute +towncrier +mypy-protobuf +azure-mgmt-containerinstance +mypy-boto3-dynamodb +pytest-split +ua-parser-builtins +jwcrypto +azure-data-tables +snowplow-tracker +adlfs +face +glom +parse-type +griffe +ndg-httpsclient +pdf2image +pathlib +dirac +mkdocs-get-deps +types-pyopenssl +azure-mgmt-keyvault +gensim +django-storages +apache-airflow-providers-smtp +numexpr +pathable +opentelemetry-distro +azure-mgmt-authorization +types-cachetools +sphinxcontrib-jquery +pyhcl +fakeredis +google-ai-generativelanguage +prison +django-extensions +gsutil +python-socketio +hatch-vcs +mypy-boto3-lambda +tf-keras-nightly +python-engineio +eth-account +minio +google-generativeai +unstructured-client +llama-index +apache-airflow-providers-common-compat +clickclick +configobj +swagger-ui-bundle +mypy-boto3-secretsmanager +appnope +stringcase +fasttext-wheel +azure-batch +dpath +setuptools-rust +paginate +pathlib2 +azure-monitor-opentelemetry-exporter +llama-index-core +azure-devops +ffmpy +kfp-pipeline-spec +peewee +elasticsearch-dsl +jsii +dbt-snowflake +pyarrow-hotfix +pyphen +webob +azure-mgmt-network +hydra-core +python-pptx +pyaml +azure-graphrbac +python-decouple +pkce +langchain-google-community +autograd +shortuuid +blessed +opentelemetry-instrumentation-logging +user-agents +nvidia-ml-py +atomicwrites +pipx +azure-kusto-ingest +xyzservices +addict +pytest-randomly +elementpath +dask-expr +futures +pyelftools +simple-websocket +types-toml +pdbr +atlassian-python-api +jsonschema-path +ipaddress +pooch +pymsteams +toposort +amazon-ion +teradatasql +evergreen-py +unittest-xml-reporting +pathlib-abc +pgvector +html2text +hatch +apache-airflow-providers-common-io +geomet +uritools +semgrep +types-cffi +azure-mgmt-containerservice +publication +hf-transfer +pathvalidate +codeowners +pkgconfig +langgraph +openlineage-python +fixedint +cog +fiona +py-spy +apache-airflow-providers-amazon +expiringdict +mypy-boto3-cloudformation +dictdiffer +recordlinkage +singer-sdk +schedule +spark-nlp +motor +distributed +boolean-py +funcsigs +xmlschema +pgpy +mypy-boto3-ec2 +llama-index-indices-managed-llama-cloud +mypy-boto3-appflow +uamqp +pypika +weasyprint +apache-airflow-providers-microsoft-mssql +async-generator +bokeh +cx-oracle +azure-mgmt-datalake-store +whitenoise +feedparser +sqlmodel +license-expression +python-snappy +pytest-json-report +django-redis +pypdfium2 +packageurl-python +unidiff +questionary +click-default-group +timezonefinder +xlwt +cmdstanpy +sqlalchemy-redshift +tokenize-rt +azure-mgmt-monitor +aws-cdk-asset-awscli-v1 +prometheus-fastapi-instrumentator +dbt-postgres +genson +optuna +launchdarkly-server-sdk +py-partiql-parser +locust +kaleido +hdfs +prophet +python-jenkins +python3-openid +aws-cdk-integ-tests-alpha +cyclonedx-python-lib +marshmallow-dataclass +msgraph-core +psycopg-pool +hexbytes +azure-mgmt-redis +altgraph +grpcio-reflection +azure-mgmt-rdbms +azure-mgmt-web +cerberus +clickhouse-driver +nvidia-cublas-cu11 +sqlfluff +colorful +colour +azure-mgmt-sql +datasketch +patchelf +cassandra-driver +socksio +flower +kfp-server-api +enum-compat +deepmerge +olefile +openlineage-integration-common +eth-rlp +geventhttpclient +atpublic +lxml-html-clean +pydocstyle +asyncssh +azure-appconfiguration +aiohttp-cors +django-debug-toolbar +azure-monitor-opentelemetry +azure-mgmt-dns +nvidia-cudnn-cu11 +acryl-datahub +myst-parser +mypy-boto3-sts +azure-core-tracing-opentelemetry +pyogrio +azure-mgmt-servicebus +blobfile +types-six +convertdate +pprintpp +uuid +google-cloud-datastore +pyhumps +azure-mgmt-eventhub +azure-mgmt-msi +ffmpeg-python +cohere +python-telegram-bot +azure-mgmt-cdn +opentelemetry-resource-detector-azure +notion-client +truststore +pyinstaller-hooks-contrib +google-cloud-bigquery-biglake +dynamodb-json +readchar +robotframework +svgwrite +azure-mgmt-loganalytics +azure-monitor-query +orbax-checkpoint +datamodel-code-generator +behave +langgraph-checkpoint +azure-mgmt-cognitiveservices +azure-mgmt-search +pyinstaller +pdfplumber +azure-mgmt-managementgroups +librosa +chevron +fpdf +xformers +roman-numerals-py +azure-mgmt-batch +zopfli +maturin +nvidia-cuda-runtime-cu11 +formulaic +cytoolz +peft +djangorestframework-simplejwt +nbclassic +pyiceberg +nvidia-cuda-nvrtc-cu11 +pytest-repeat +xmlsec +chromadb +qdrant-client +azure-mgmt-trafficmanager +drf-spectacular +llama-index-llms-openai +apache-flink +nox +yq +azure-mgmt-marketplaceordering +pydyf +jwt +environs +azure-mgmt-applicationinsights +unearth +py-serializable +azure-mgmt-nspkg +w3lib +korean-lunar-calendar +dependency-groups +web3 +pemja +azure-mgmt-iothub +azure-mgmt-recoveryservicesbackup +azure-mgmt-recoveryservices +interface-meta +diffusers +django-environ +python-crontab +apache-flink-libraries +prometheus-flask-exporter +imageio-ffmpeg +bitsandbytes +azure-mgmt-advisor +azure-mgmt-eventgrid +vcrpy +flit +db-contrib-tool +knack +memray +requirements-parser +mkdocstrings +flask-restful +sse-starlette +smbprotocol +catboost +uuid6 +langgraph-sdk +azure-cosmosdb-table +urwid +pynamodb +keras-preprocessing +eth-hash +tensorboard-plugin-wit +nested-lookup +allure-python-commons +types-jsonschema +pdpyras +autoflake +azure-mgmt-media +azure-mgmt-servicefabric +azure-mgmt-billing +azure-mgmt-policyinsights +azure-cosmosdb-nspkg +sklearn +dockerfile-parse +num2words +azure-mgmt-iothubprovisioningservices +azure-mgmt-batchai +dunamai +azure-synapse-artifacts +azure-mgmt-signalr +pygit2 +azure-mgmt-datalake-nspkg +azure-mgmt-datamigration +azure-mgmt-maps +mypy-boto3-redshift-data +azure-mgmt-iotcentral +stanio +pypandoc +mongomock +opentelemetry-instrumentation-sqlalchemy +s3path +eth-utils +pyzipper +testcontainers +rustworkx +influxdb +sphinx-autodoc-typehints +ultralytics-thop +pulumi +hatch-fancy-pypi-readme +opentelemetry-instrumentation-grpc +voluptuous +mixpanel +python-box +pikepdf +pytz-deprecation-shim +proglog +pre-commit-uv +geojson +netifaces +strip-hints +lifelines +mkdocs-autorefs +hishel +pytesseract +cssutils +terminaltables +inputimeout +memory-profiler +aws-cdk-lib +python3-saml +parsimonious +flask-migrate +zipfile38 +evaluate +pathlib-mate +pinotdb +google-cloud-pubsublite +apache-airflow-providers-sftp +yt-dlp +aws-psycopg2 +haversine +pytest-custom-exit-code +orderedmultidict +databricks-api +simpleeval +zstd +pyhocon +yfinance +google-analytics-data +flake8-bugbear +hjson +types-pillow +pyxlsb +qtpy +anytree +pytest-benchmark +eth-typing +cloudflare +url-normalize +objsize +paho-mqtt +django-timezone-field +moviepy +influxdb-client +dicttoxml +furl +pyhanko +dash-core-components +inject +lightning +azure-functions +databricks-connect +microsoft-kiota-serialization-text +audioread +fake-useragent +aiokafka +appium-python-client +rollbar +pystache +microsoft-kiota-abstractions +tensorflow-metadata +dash-html-components +temporalio +pyhive +dash-table +azure-search-documents +sqlglotrs +sphinx-copybutton +backrefs +supervisor +allure-pytest +pyreadline3 +dirtyjson +sqlparams +python-socks +azure-storage-file +github-heatmap +facebook-business +plotnine +multimethod +microsoft-kiota-serialization-json +pyinstrument +json-repair +tensorboardx +bc-detect-secrets +cbor2 +flaky +opentelemetry-instrumentation-redis +python-bidi +dm-tree +striprtf +dep-logic +scikit-build-core +requests-aws-sign +pdm +thrift-sasl +azureml-core +ansible-compat +azure-synapse-spark +aiorwlock +opentelemetry-instrumentation-aiohttp-client +types-certifi +oss2 +asana +pmdarima +azure-eventgrid +launchdarkly-eventsource +tablib +torchsde +unstructured +vllm +reactivex +types-psycopg2 +poetry-dynamic-versioning +mizani +pymeeus +pex +sphinx-design +opentelemetry-instrumentation-botocore +qtconsole +pyzstd +funcy +arpeggio +pytest-ordering +trampoline +spdx-tools +markdown2 +jdcal +types-html5lib +thefuzz +pypiwin32 +construct +pytest-order +piexif +django-celery-beat +pytest-sugar +aiomultiprocess +pypng +py7zr +newrelic +rasterio +jsonpath-python +syrupy +ollama +moreorless +pycocotools +python-crfsuite +apache-airflow-microsoft-fabric-plugin +opentelemetry-instrumentation-httpx +datefinder +langchain-aws +gprof2dot +cleanco +dependency-injector +openlineage-airflow +simple-gcp-object-downloader +zope-deprecation +policy-sentry +dynaconf +parver +cloudsplaining +multipledispatch +pytest-socket +opencv-contrib-python +pycep-parser +pytest-base-url +icdiff +eth-keys +hupper +kaitaistruct +python-consul +llama-index-readers-file +hyperopt +neo4j +aws-cdk-asset-node-proxy-agent-v6 +bc-python-hcl2 +eth-abi +pypyp +pastedeploy +azure-cli-core +llama-index-agent-openai +puremagic +webargs +rlp +repoze-lru +pyqt5-sip +boxsdk +instructor +llama-index-readers-llama-parse +azure-mgmt-devtestlabs +pyqt5 +dbt-spark +bc-jsonpath-ng +tf-keras +icalendar +pastel +sgmllib3k +eth-keyfile +dj-database-url +django-stubs-ext +promise +kornia +drf-yasg +std-uritemplate +scandir +editorconfig +requests-sigv4 +zict +mbstrdecoder +open-clip-torch +pytest-subtests +llama-index-embeddings-openai +aiocache +multi-key-dict +opentelemetry-propagator-aws-xray +concurrent-log-handler +pygeohash +jq +trailrunner +langfuse +graphframes +gguf +o365 +stdlibs +pycurl +hijri-converter +langchain-anthropic +soxr +osqp +types-simplejson +venusian +pytest-icdiff +typepy +usort +databricks-pypi1 +requests-cache +types-python-slugify +llama-index-program-openai +versioneer +lm-format-enforcer +fastcore +django-stubs +yaspin +lupa +boto-session-manager +sentinels +ec2-metadata +mistral-common +pyppmd +cheroot +jsbeautifier +python-editor +pybcj +affine +llama-index-multi-modal-llms-openai +ufmt +s3pathlib +pyhamcrest +iterproxy +tld +google-apitools +outlines +sounddevice +pydata-sphinx-theme +mypy-boto3-ssm +translationstring +comtypes +llama-index-cli +sacrebleu +eventlet +llama-cloud-services +pdm-backend +simsimd +curlify +types-psutil +multitasking +parsel +apprise +apache-airflow-providers-postgres +multivolumefile +findspark +pytest-messenger +pyfakefs +pefile +dbt-databricks +llama-index-question-gen-openai +papermill +base58 +pymemcache +redis-py-cluster +compressed-tensors +pygsheets +bazel-runfiles +publicsuffix2 +mypy-boto3-iam +func-args +sqlalchemy2-stubs +folium +ckzg +python-arango +pynndescent +chispa +pyaes +trimesh +channels +dagster +azure-mgmt-subscription +munch +branca +async-property +clickhouse-sqlalchemy +braceexpand +llama-cloud +types-mock +types-beautifulsoup4 +weread2notionpro +pyramid +umap-learn +types-retry +pyyaml-include +interegular +expandvars +json-log-formatter +wget +lru-dict +diff-match-patch +joserfc +plaster +pyfiglet +plaster-pastedeploy +google-cloud-recommendations-ai +prefect-aws +pymongo-auth-aws +hologram +xgrammar +plumbum +pymupdfb +scapy +aws-cdk-cloud-assembly-schema +asyncer +opentelemetry-exporter-prometheus +pulumi-aws +pygame +subprocess-tee +bitstring +snowflake +rtree +google-genai +snowflake-core +bashlex +python-hcl2 +dbt-bigquery +casefy +python-rapidjson +conan +requests-futures +etils +injector +txaio +tensorflow-hub +boto3-type-annotations +types-freezegun +pep8-naming +pillow-heif +dbutils +aio-pika +flask-httpauth +checkdigit +polling +pybloom-live +click-spinner +pyqt5-qt5 +auth0-python +autobahn +pytest-playwright +django-appconf +pylint-plugin-utils +cairosvg +inquirer +singledispatch +aiolimiter +opsgenie-sdk +tweepy +pyquery +javaproperties +soda-core-spark-df +googlemaps +bottleneck +pyusb +boa-str +pykwalify +pywinpty +cvxpy +onnxruntime-gpu +azure-cli +safehttpx +azure-cli-telemetry +inflate64 +cli-exit-tools +rq +cairocffi +kazoo +lib-detect-testenv +dagster-pipes +netcdf4 +langchain-experimental +soda-core-spark +azure-mgmt-datalake-analytics +pdfkit +sacremoses +fpdf2 +pamqp +social-auth-core +jproperties +realtime +wordcloud +azure-mgmt-apimanagement +speechrecognition +partial-json-parser +youtube-transcript-api +dbt-redshift +testfixtures +azure-mgmt-privatedns +django-js-asset +avro-gen3 +cdk-nag +django-model-utils +gotrue +openxlab +ifaddr +azure +lmdb +priority +ably +prance +flask-restx +patch-ng +imagehash +autograd-gamma +slack-bolt +aiofile +pint +discord-py +arviz +pympler +decord +pyclipper +pytest-instafail +ip3country +cftime +gcovr +codespell +types-aiobotocore +immutables +azure-mgmt-hdinsight +ansible-lint +azure-mgmt-reservations +pyairtable +pure-sasl +flatten-json +category-encoders +pinecone-plugin-interface +grpclib +sseclient-py +shtab +giturlparse +mypy-boto3-apigateway +groq +yamale +wrapt-timeout-decorator +asgi-lifespan +caio +respx +vulture +django-phonenumber-field +gdown +python-keycloak +pyramid-debugtoolbar +dagster-graphql +mitmproxy +virtualenv-clone +tensorstore +pyramid-mako +pulp +scs +azure-loganalytics +premailer +aioquic +flask-bcrypt +pycomposefile +pymilvus +azure-mgmt-security +aws-cdk-asset-kubectl-v20 +aws-secretsmanager-caching +circuitbreaker +pylsqpack +pytest-unordered +azure-mgmt-synapse +mypy-boto3-kinesis +pytest-dotenv +safety +microsoft-kiota-serialization-multipart +j2cli +django-celery-results +mysql-connector +restructuredtext-lint +azure-mgmt-consumption +supabase +pip-requirements-parser +dbl-tempo +gspread-dataframe +htmldate +python-ldap +pyramid-jinja2 +types-tqdm +microsoft-kiota-serialization-form +port-for +databricks-pypi2 +snowflake-legacy +python-xlib +albucore +lit +pulumi-command +social-auth-app-django +probableparsing +sarif-om +pyunormalize +usaddress +types-aiobotocore-s3 +commentjson +jschema-to-python +python-iso639 +tecton +mistralai +rfc3987 +timeout-decorator +pynvml +accessible-pygments +structlog-sentry +rembg +riot +confuse +kornia-rs +types-pygments +azure-mgmt-appconfiguration +pinecone-client +impyla +backports-functools-lru-cache +apache-airflow-providers-mongo +node-semver +oci +mypy-boto3-stepfunctions +tensorflow-datasets +pulumi-tls +azure-multiapi-storage +azure-mgmt-relay +azure-mgmt-netapp +azure-synapse-accesscontrol +azure-mgmt-sqlvirtualmachine +azure-mgmt-redhatopenshift +robotframework-pythonlibcore +types-cryptography +curl-cffi +supafunc +blake3 +supervision +mleap +zeroconf +postgrest +vertica-python +ortools +yacs +tox-uv +mirakuru +azure-mgmt-botservice +aiomysql +types-ujson +aiormq +flask-openid +sphinx-autobuild +azure-mgmt-notificationhubs +flashtext +webdataset +openlineage-sql +pywinrm +mitmproxy-rs +storage3 +azure-mgmt-databoxedge +daphne +soda-core +iopath +apache-sedona +azure-synapse-managedprivateendpoints +azure-keyvault-administration +requests-unixsocket +azure-mgmt-extendedlocation +pywinauto +fasttext +azure-mgmt-imagebuilder +crossplane +pymatting +salesforce-bulk +azure-mgmt-servicefabricmanagedclusters +azure-mgmt-appcontainers +cloud-sql-python-connector +natto-py +hypercorn +azure-mgmt-servicelinker +filterpy +dataproperty +tensorflowjs +strawberry-graphql +flake8-docstrings +mypy-boto3-ecr +tree-sitter-python +python-vagrant +azure-mgmt-logic +polyfactory +koalas +leb128 +multipart +ddapm-test-agent +docker-compose +mypy-boto3-athena +biopython +pytest-check +apache-airflow-providers-jdbc +editdistance +pip-api +segment-analytics-python +astropy +polib +arabic-reshaper +cssbeautifier +mammoth +types-markupsafe +tabledata +uwsgi +cobble +cmd2 +azure-servicefabric +pybuildkite +django-crispy-forms +tcolorpy +pytablewriter +presidio-analyzer +types-jinja2 +robotframework-requests +clarabel +lakefs-sdk +sphinxcontrib-mermaid +embedchain +pyandoc +robotframework-seleniumlibrary +opencv-contrib-python-headless +hubspot-api-client +stepfunctions +groovy +azure-mgmt +aliyun-python-sdk-kms +azure-schemaregistry +azure-mgmt-powerbiembedded +pyudev +asynch +azure-mgmt-commerce +azure-mgmt-scheduler +ansicolors +pyluach +hdbcli +mangum +azure-mgmt-hanaonazure +djlint +azure-mgmt-machinelearningcompute +azure-mgmt-managementpartner +mcp +xsdata +gymnasium +scrapy +protego +django-ipware +adapters +tzfpy +poetry-plugin-pypi-mirror +azure-ai-ml +azure-servicemanagement-legacy +ghapi +qdldl +ecs-logging +dataclass-wizard +pytest-httpx +pyhanko-certvalidator +flatdict +pytest-assume +zarr +pygtrie +pipelinewise-singer-python +flax +azure-mgmt-devspaces +opentelemetry-sdk-extension-aws +azure-ai-documentintelligence +queuelib +xhtml2pdf +mmcif +shrub-py +a2wsgi +poethepoet +ibm-db +signxml +ydata-profiling +statsig +cliff +types-boto3 +bump2version +azure-applicationinsights +pysmi +langchain-google-genai +biotite +pytest-httpserver +anyascii +coreapi +oras +django-import-export +python-ulid +smartsheet-python-sdk +attrdict +pyppeteer +sqlfluff-templater-dbt +pydispatcher +stringzilla +sqlalchemy-stubs +django-simple-history +pyspark-dist-explore +portpicker +json-delta +logzero +dlt +apache-airflow-providers-microsoft-azure +itypes +bitstruct +msoffcrypto-tool +tableauhyperapi +dohq-artifactory +docx2txt +pandasql +backports-weakref +svglib +dagster-postgres +igraph +dparse +python-pam +win32-setctime +channels-redis +clang-format +modin +dagster-webserver +geoalchemy2 +colorclass +marshmallow-jsonschema +mando +xmod +country-converter +shareplum +google-cloud-trace +ctranslate2 +numcodecs +zenpy +dataclasses-avroschema +jsonconversion +jsonpath-rw +python-can +blosc2 +editor +memoization +runs +seleniumbase +nvidia-cusolver-cu11 +pinecone +duckduckgo-search +mongoengine +ansi2html +flexparser +flask-compress +pytest-bdd +msgraph-sdk +nvidia-cuda-cupti-cu11 +flexcache +nvidia-curand-cu11 +furo +nvidia-cusparse-cu11 +tree-sitter-javascript +radon +python-stdnum +itemadapter +nvidia-cufft-cu11 +pusher +types-boto +firecrawl-py +opentelemetry-instrumentation-sqlite3 +mypy-boto3-logs +keystoneauth1 +microsoft-security-utilities-secret-masker +algoliasearch +sudachipy +aws-cdk-aws-lambda-python-alpha +dagster-aws +pytimeparse2 +hyperpyyaml +about-time +testpath +flake8-isort +click-log +ndjson +mypy-boto3-xray +grpc-stubs +itemloaders +nvidia-nvtx-cu11 +mypy-boto3-schemas +xmlrunner +backports-datetime-fromisoformat +jinja2-simple-tags +pycairo +flask-marshmallow +c7n +mypy-boto3-signer +flatten-dict +alive-progress +teradatasqlalchemy +tritonclient +speechbrain +autopage +intelhex +sshpubkeys +opentracing +cached-path +segment-anything +nanoid +flake8-comprehensions +dogpile-cache +farama-notifications +find-libpython +deptry +types-click +azure-mgmt-postgresqlflexibleservers +pylev +pytest-postgresql +devtools +mypy-boto3-kms +pylint-django +opentelemetry-instrumentation-jinja2 +azure-mgmt-mysqlflexibleservers +diracx-client +rope +pylance +pyahocorasick +lark-parser +textblob +nvidia-nccl-cu11 +mediapipe +tinyhtml5 +sigtools +mypy-boto3-sns +coolname +awkward +opentelemetry-instrumentation-threading +backports-tempfile +aiogram +cadwyn +opentelemetry-instrumentation-asyncio +aws-encryption-sdk +ibm-cloud-sdk-core +logging-azure-rest +vertexai +h5netcdf +pyrate-limiter +databricks +pytest-profiling +spandrel +python-baseconv +flask-shell-ipython +suds-community +trl +sphinx-basic-ng +faster-whisper +awkward-cpp +mutagen +airportsdata +github3-py +ccxt +inflector +jaconv +xattr +webtest +sanic +chex +aws-sam-cli +lancedb +retry2 +thop +fastrlock +odfpy +starlette-context +azureml-dataprep +jupytext +prefect-gcp +docxtpl +primp +opentelemetry-instrumentation-celery +yarg +dvc +hatch-requirements-txt +pytest-aiohttp +idna-ssl +django-allauth +langgraph-prebuilt +apache-airflow-providers-airbyte +boostedblob +azure-mgmt-hybridcompute +jieba +python-lsp-jsonrpc +mss +pyerfa +ray-elasticsearch +setuptools-git-versioning +modelscope +flask-admin +rouge-score +regress +tables +aws-lambda-builders +ulid-py +phonenumberslite +djangorestframework-stubs +shyaml +dbfread +pysaml2 +marko +py-ecc +cerberus-python-client +visions +exchangelib +apache-airflow-providers-odbc +protoc-gen-openapiv2 +jsonargparse +numpy-financial +pybase64 +uproot +intervaltree +tensorflow-addons +rx +cinemagoer +dpkt +s3cmd +aioredis +aiostream +dominate +urllib3-secure-extra +hashids +resampy +keyrings-alt +dotenv +path +click-help-colors +pymisp +typish +credstash +doit +oslo-utils +oyaml +dash-bootstrap-components +dockerpty +dropbox +ephem +apache-airflow-providers-apache-spark +python-oxmsg +imdbpy +types-decorator +betterproto +kneed +pipreqs +jsonmerge +rcssmin +pyupgrade +cloudscraper +pyvmomi +outlines-core +django-prometheus +mkdocs-monorepo-plugin +cuda-python +wand +rank-bm25 +sphinx-argparse +graypy +django-csp +dbt-fabric +pudb +panel +pyquaternion +pyvirtualdisplay +python-geohash +jaxtyping +nameparser +newspaper3k +pytest-dependency +mypy-boto3-cloudwatch +wasmer +lunardate +pytoolconfig +pymongocrypt +types-colorama +modal +opentelemetry-instrumentation-boto3sqs +apsw +justext +readerwriterlock +optax +rstr +datacompy +pyvis +mypy-boto3-lakeformation +cheetah3 +azureml-dataprep-rslex +xarray-einstats +apache-airflow-providers-dbt-cloud +wmi +pep8 +django-otp +gspread-formatting +fastapi-pagination +tlparse +array-record +pinecone-plugin-inference +cupy-cuda12x +html-text +config +mkdocs-macros-plugin +tableau-api-lib +jupyter-server-ydoc +flake8-pyproject +pyscreeze +python-certifi-win32 +pynput +swifter +tensorflow-cpu +k8 +textdistance +safety-schemas +wasmer-compiler-cranelift +glob2 +mypy-boto3-dataexchange +west +asgi-correlation-id +google-cloud-discoveryengine +django-countries +htmlmin +tf-nightly +flask-socketio +dnslib +versioneer-518 +c7n-org +tree-sitter-yaml +azure-mgmt-resourcegraph +func-timeout +jupyter-ydoc +ndindex +troposphere +crc32c +opentelemetry-resourcedetector-gcp +detect-secrets +peppercorn +janus +pytest-freezegun +opentelemetry-exporter-gcp-trace +sgqlc +django-picklefield +cron-validator +pysbd +python-amazon-sp-api +crewai +braintree +pretty-html-table +flake8-polyfill +coreschema +depyf +azure-storage +ebcdic +elastic-apm +easydict +types-werkzeug +synchronicity +pytube +django-health-check +requests-auth-aws-sigv4 +pillow-avif-plugin +ibm-platform-services +raven +cuda-bindings +update-checker +onnxconverter-common +awslambdaric +jsons +ldaptor +gluonts +sqlalchemy-trino +triad +fugue +dirty-equals +types-ipaddress +presto-python-client +result +slowapi +pyroute2 +plac +opencensus-ext-logging +artifacts-keyring +tfds-nightly +grimp +contextvars +apache-airflow-providers-oracle +torchdiffeq +legacy-cgi +y-py +curatorbin +proxy-protocol +workalendar +ocspbuilder +ocspresponder +evergreen-lint +oslo-config +typeid-python +recommonmark +py-deviceid +opentelemetry-instrumentation-asyncpg +gssapi +tyro +import-deps +coveralls +adagio +mkdocs-git-revision-date-localized-plugin +polling2 +django-silk +oslo-i18n +django-oauth-toolkit +python-on-whales +jupyter-server-fileid +ptpython +azure-ai-formrecognizer +zc-lockfile +jsonschema-spec +types-openpyxl +pdoc +pysnmp +eradicate +flask-talisman +progress +ormsgpack +pybytebuffer +check-jsonschema +ml-collections +sktime +spinners +pykmip +argparse-addons +log-symbols +aiosmtplib +policyuniverse +requests-oauth +fast-depends +flake8-eradicate +pytweening +geocoder +tensorflow-probability +transitions +pyexcel-io +logzio-python-handler +azure-schemaregistry-avroserializer +trafilatura +opentelemetry-instrumentation-aws-lambda +certbot-dns-cloudflare +gitdb2 +envyaml +lml +numpydoc +pyautogui +requests-kerberos +ypy-websocket +pyairports +tree-sitter-java +quart +dnachisel +langchain-ollama +myst-nb +sklearn-compat +future-fstrings +geckodriver-autoinstaller +jupyter-cache +python-ipware +isal +milvus-lite +pyrect +treelib +pygetwindow +tree-sitter-xml +testtools +uncertainties +opentelemetry-instrumentation-system-metrics +freetype-py +starlette-exporter +apache-airflow-providers-apache-kafka +types-flask +optimum +ratelim +linecache2 +libsass +ntlm-auth +azureml-mlflow +statsforecast +checksumdir +pip-audit +inquirerpy +traceback2 +scikit-base +flake8-import-order +mouseinfo +oslo-serialization +sanic-routing +pyenchant +courlan +apache-airflow-providers-salesforce +rjsmin +pysmb +dict2xml +clang +elementary-data +azure-ai-inference +phik +python-calamine +pfzy +pydruid +kconfiglib +jinja2-humanize-extension +mkdocs-literate-nav +types-chardet +flake8-builtins +types-stripe +tree-sitter-go +pymsgbox +flask-mail +undetected-chromedriver +textparser +super-collections +mypy-boto3-ecs +dotty-dict +snakeviz +tree-sitter-rust +latexcodec +opentelemetry-instrumentation-pika +decli +types-httplib2 +flake8-print +verboselogs +pyarrow-stubs +asteval +tree-sitter-cpp +mongo-tooling-metrics +opentelemetry-exporter-prometheus-remote-write +pluginbase +django-taggit +debtcollector +apache-airflow-providers-pagerduty +cachy +mongo-ninja-python +pybtex +mypy-boto3-emr +pyshp +shellescape +emr-notebooks-magics +volcengine-python-sdk +pyexasol +pytest-github-actions-annotate-failures +docxcompose +yappi +facexlib +line-profiler +simple-parsing +gnupg +tensorflow-intel +icecream +schwifty +commitizen +honcho +naked +josepy +os-service-types +python-codon-tables +lhcbdirac +onfido-python +diagrams +httpretty +mypy-boto3-elbv2 +aiodataloader +autofaker +chromedriver-autoinstaller +google-cloud-pipeline-components +hmsclient +types-regex +certifi-linux +sphinx-autoapi +python-cinderclient +django-anymail +pylatexenc +pytelegrambotapi +lbenv +shopifyapi +pyopengl +sampleproject +grapheme +lunarcalendar +rfc3339 +dagster-pandas +types-dateparser +sasl +lbplatformutils +lbcondawrappers +google-cloud-alloydb +stone +pdbp +mercantile +codecov +llama-index-legacy +lb-telemetry +xenv +textwrap3 +airbyte-cdk +airbyte-api +macholib +livy +bson +pytest-memray +pynose +django-formtools +gdbmongo +html-testrunner +patterns +comfyui-frontend-package +pylru +tabcompleter +mypy-boto3-ses +pytest-factoryboy +openapi-core +brotlipy +django-mptt +extract-msg +extras +itables +fastapi-utils +argh +google-cloud-scheduler +cibuildwheel +fastprogress +anybadge +openinference-semantic-conventions +opentelemetry-instrumentation-starlette +docusign-esign +pytest-lazy-fixture +httmock +treescope +typing-utils +dagster-k8s +types-lxml +pyactiveresource +pip-licenses +apache-airflow-providers-datadog +awscliv2 +llama-index-llms-azure-openai +azure-monitor-ingestion +pytest-retry +amazon-textract-response-parser +flask-smorest +torchao +crewai-tools +types-aiobotocore-sqs +red-discordbot +langchain-cohere +dagster-slack +mockito +tavily-python +datadog-lambda +torchtext +clr-loader +google-cloud-error-reporting +ansiwrap +kgb +wurlitzer +sphinx-book-theme +astropy-iers-data +tinydb +sphinx-tabs +ddt +python-frontmatter +aws-msk-iam-sasl-signer-python +subprocess32 +mypy-boto3-route53 +dagster-dbt +crccheck +docformatter +scons +opentelemetry-instrumentation-pymongo +easyocr +untokenize +mypy-boto3-sagemaker +nbsphinx +tree-sitter-bash +dbus-fast +openstacksdk +tree-sitter-html +types-pyserial +pytest-recording +celery-redbeat +pre-commit-hooks +property-manager +quantlib +django-compressor +utilsforecast +tree-sitter-css +js2py +tree-sitter-json +jenkinsapi +scenedetect +tree-sitter-sql +tree-sitter-regex +model-bakery +tree-sitter-toml +tree-sitter-markdown +param +restrictedpython +matrix-client +pyexcel +sphinxcontrib-websupport +localstack-core +scikit-build +nose2 +vtk +types-pkg-resources +uuid-utils +guppy3 +types-enum34 +django-ses +opentelemetry-resourcedetector-kubernetes +asyncstdlib +google-api-python-client-stubs +nibabel +bootstrap-flask +scikit-optimize +paste +backports-cached-property +tqdm-multiprocess +rpaframework +drf-nested-routers +opentelemetry-instrumentation-tortoiseorm +tinysegmenter +tensorflow-io +openapi-schema-pydantic +mkdocs-section-index +flyteidl +jinja2-cli +parsy +oci-cli +crypto +pytest-watch +presidio-anonymizer +iterative-telemetry +aws-cdk-aws-glue-alpha +jsonpath +urwid-readline +pytorch-metric-learning +shiboken6 +pip-system-certs +google-cloud-artifact-registry +ariadne +dvc-data +zigpy +clean-fid +platformio +pulsar-client +google-cloud-iam +fido2 +cli-helpers +git-remote-codecommit +singer-python +watchgod +opentelemetry-resourcedetector-docker +cmaes +pyside6-essentials +luqum +pytest-vcr +jiwer +ecos +pythonnet +firebolt-sdk +httpx-ws +flake8-quotes +gs-quant +jinja2-time +pyside6 +pytest-azurepipelines +formic2 +xopen +openai-whisper +strict-rfc3339 +mapbox-earcut +open3d +issubclass +tbats +annoy +appdirs-stubs +stomp-py +pytest-snapshot +dateutils +import-linter +easygui +recurring-ical-events +pyre-extensions +pyside6-addons +smmap2 +tink +watchdog-gevent +stdlib-list +databricks-feature-engineering +mxnet +elasticsearch-dbapi +setuptools-git +tree-sitter-languages +databricks-pypi-extras +flufl-lock +looseversion +nodejs-wheel-binaries +types-maxminddb +validate-email +ntplib +oic +django-polymorphic +pypsrp +intuit-oauth +aiohttp-jinja2 +python-openstackclient +azureml-dataprep-native +breathe +skl2onnx +x-wr-timezone +types-aiobotocore-dataexchange +airflow-provider-great-expectations +boto3-stubs-lite +types-futures +roundrobin +graphene-django +typeshed-client +eyes-common +frictionless +mem0ai +ruamel-yaml-jinja2 +nulltype +llama-index-embeddings-azure-openai +eyes-selenium +snuggs +sbvirtualdisplay +requestsexceptions +pyqt6 +awacs +tentaclio +types-pyasn1 +docstring-to-markdown +delta +mypy-boto3-autoscaling +pypinyin +python-keystoneclient +lmfit +patool +pytest-celery +fvcore +django-reversion +imagecodecs +enrich +singleton-decorator +scylla-driver +apache-airflow-providers-tableau +grandalf +sqlalchemy-mate +selenium-wire +molecule +sqlakeyset +telethon +flasgger +pyexcel-xlsx +google-cloud-documentai +polyline +opentelemetry-exporter-jaeger-thrift +django-ninja +flake8-black +pyxdg +django-structlog +mypy-boto3-events +mecab-python3 +mypy-boto3-acm +unittest2 +rpyc +coreforecast +pytest-parallel +apache-airflow-providers-celery +azure-mgmt-kusto +deepface +readability-lxml +types-xmltodict +json-logging +decopatch +tentaclio-s3 +ariadne-codegen +flytekit +fredapi +uvicorn-worker +google-cloud-recaptcha-enterprise +halo +mdx-truly-sane-lists +fixtures +delta-sharing +types-termcolor +backports-strenum +django-ratelimit +flask-debugtoolbar +pyautogen +publish-event-sns +us +flask-basicauth +markuppy +core-universal +hdbscan +javaobj-py3 +flake8-noqa +requests-pkcs12 +frida +sqlalchemy-continuum +dagster-cloud +customerio +xmljson +pyshark +logz +mkdocs-redirects +azure-iot-device +django-axes +urlobject +traittypes +dspy +mkdocs-awesome-pages-plugin +python-memcached +deep-translator +pdfrw +plaid-python +celery-types +jupyter-packaging +sphinxcontrib-bibtex +django-waffle +falcon +rdrobust +colored +alibabacloud-adb20211201 +cchardet +prefect-docker +fluent-logger +rdkit +mypy-boto3-opensearch +markitdown +zope-schema +pystan +imblearn +spacy-wordnet +mypy-boto3-transfer +pytest-reportlog +netsuitesdk +apache-airflow-providers-redis +mlxtend +fhir-resources +deepspeed +colorcet +dify-plugin +python-redis-lock +publicsuffixlist +sqlalchemy-hana +uszipcode +lxml-stubs +opentelemetry-exporter-jaeger-proto-grpc +dagster-spark +xlutils +krb5 +pyglet +sudachidict-core +flametree +htmldocx +argilla +jieba3k +feedfinder2 +teamcity-messages +opentelemetry-semantic-conventions-ai +initools +types-aiobotocore-dynamodb +langchain-chroma +braintrust-core +sqlalchemy-drill +django-admin-rangefilter +magic-filter +azure-storage-nspkg +flask-oidc +sqllineage +pyqt6-qt6 +chainlit +flask-dance +pyqt6-sip +flake8-plugin-utils +akshare +wadler-lindig +jsonfield +avro-gen +opencc +pygobject +mailchimp-marketing +azure-cognitiveservices-speech +clikit +alembic-postgresql-enum +mkdocs-gen-files +pyexcel-xls +minidump +types-emoji +mypy-boto3-ebs +cxxfilt +filesplit +braintrust +html5tagger +mypy-boto3-dlm +pyviz-comms +yarn-api-client +asyncache +tabula-py +streamsets +duckdb-engine +zipfile-deflate64 +tracerite +types-docker +django-widget-tweaks +flameprof +pymannkendall +langchain-groq +versioningit +asciitree +gpustat +wikipedia +python-subunit +scmrepo +mypy-boto3-cognito-idp +plum-dispatch +yattag +gliner +camel-converter +pyfzf +pydantic-yaml +cvxopt +pydantic-ai-slim +mypy-boto3-emr-serverless +swagger-spec-validator +sqlitedict +quinn +pathtools +pot +pydicom +tensorflow-gpu +backports-shutil-get-terminal-size +kerberos +setuptools-scm-git-archive +ragas +nanobind +flake8-bandit +mypy-boto3-batch +pytest-docker +apache-airflow-client +sparqlwrapper +pyserde +pyngrok +notifiers +webauthn +rpaframework-core +apache-airflow-providers-openlineage +databases +opentelemetry-instrumentation-elasticsearch +slacker +gputil +gender-guesser +nbqa +opencc-python-reimplemented +scrapbook +pythonping +transaction +collections-extended +pydantic-xml +gcs-oauth2-boto-plugin +embedding-reader +sphinxcontrib-spelling +airflow-dbt +jstyleson +python3-logstash +arnparse +autofaiss +zope-proxy +datarobotx +pgeocode +whatthepatch +pyannote-database +pylint-pydantic +blendmodes +uuid7 +mypy-boto3-bedrock-runtime +databricks-feature-store +adyen +pybtex-docutils +appengine-python-standard +oletools +acme +drf-spectacular-sidecar +objgraph +tangled-up-in-unicode +python-fsutil +pyannote-core +ansible-base +splunk-sdk +hellosign-python-sdk +pyjks +pyannote-audio +zthreading +python-lsp-server +domdf-python-tools +envs +sparse +xatlas +autocommand +pcodedmp +zipfile36 +textfsm +tach +opentelemetry-propagator-gcp +types-flask-cors +murmurhash2 +rstcheck +gtts +jinjasql +simplegeneric +livereload +robotframework-tidy +vhacdx +synapseml +salt-lint +sphinxcontrib-httpdomain +exchange-calendars +sshconf +shandy-sqlfmt +easyprocess +lsprotocol +msgpack-types +pytest-ansible +aws-assume-role-lib +spyne +pyjarowinkler +azureml-dataset-runtime +svg-path +check-manifest +deepeval +manhole +manifold3d +ydb +assertpy +verspec +e2b +pyannote-metrics +gcloud +fixedwidth +hashin +office365 +pyod +rpaframework-pdf +apipkg +scikeras +tox-gh-actions +google-search-results +unsloth +gnureadline +pyserial-asyncio +pydrive2 +resize-right +sunshine-conversations-client +types-tzlocal +prometheus-api-client +semantic-kernel +databind-json +dotmap +django-tables2 +robocorp-storage +submitit +coverage-badge +types-orjson +jaraco-text +mypy-boto3 +dvc-render +miscreant +json-stream-rs-tokenizer +groundingdino-py +jsmin +pydantic-ai +tbb +torch-geometric +mkdocs-click +defusedcsv +robotframework-seleniumtestability +robocorp-vault +alexapy +pypruningradixtrie +ruptures +fancycompleter +executor +beniget +arch +ratelimiter +pgspecial +asn1 +uptrace +cvss +word2number +connectorx +logfire-api +flash-attn +pytest-deadfixtures +opentelemetry-instrumentation-kafka-python +dvc-objects +keyboard +graspologic +brotlicffi +json2html +elevenlabs +rarfile +webvtt-py +table-logger +pdbpp +pymodbus +sqltap +darglint +osc-lib +plette +python-barcode +pydantic-graph +opentelemetry-instrumentation-openai +torchdata +treepoem +ipympl +opentok +msgpack-python +opentelemetry-instrumentation-pymysql +hunter +pytest-nunit +databind-core +opentelemetry-instrumentation-boto +jsonpath-rw-ext +autoevals +parsley +google-python-cloud-debugger +django-colorfield +backports-ssl-match-hostname +usearch +robotframework-pabot +evidently +libretranslatepy +solders +ibm-db-sa +docker-image-py +argparse-dataclass +blessings +djangorestframework-api-key +pybase62 +delta-kernel-rust-sharing-wrapper +capstone +patch +wmctrl +kubernetes-client +fairscale +pgcli +audioop-lts +pyston +suds-py3 +translate +pyston-autoload +buildkite-test-collector +language-tags +glfw +rtfde +crayons +mypy-boto3-firehose +cyclopts +json-ref-dict +basedpyright +django-treebeard +fasttext-langdetect +brickflows +robotframework-stacktrace +dbt-athena-community +eralchemy +imgaug +opentelemetry-instrumentation-aiohttp-server +azure-communication-email +cartopy +spython +autodoc-pydantic +opentelemetry-instrumentation-tornado +pytest-testinfra +python-logging-loki +java-access-bridge-wrapper +praw +pi-heif +ibm-cos-sdk +rich-rst +mdformat +postmarker +pynput-robocorp-fork +pycognito +kedro +python-semantic-release +taskgroup +crispy-bootstrap5 +importlib +ibm-cos-sdk-core +splunk-handler +plyvel +python-redis-rate-limit +aiopg +googleads +pyapns-client +sharepy +symengine +b2sdk +mypy-boto3-cognito-identity +ibm-cos-sdk-s3transfer +fastdiff +z3-solver +prawcore +nc-py-api +mistletoe +testing-common-database +lazyasd +djangorestframework-csv +pyaudio +requests-html +bingads +mailchimp-transactional +google-cloud-managedkafka +python-miio +diceware +tatsu +mkdocs-panzoom-plugin +missingpy +dbt-duckdb +dvc-task +kafka-python-ng +latex2mathml +aws-embedded-metrics +codetiming +lingua-language-detector +snapshottest +biotraj +psygnal +langchain-huggingface +seeuletter +simpleflow +azureml-telemetry +dvc-studio-client +opentelemetry-propagator-jaeger +xdoctest +optbinning +notion +holoviews +palettable +lief +ansible-runner +isoweek +retry-decorator +flask-threads +pymeta3 +webrtcvad-wheels +pygls +asteroid-filterbanks +pytest-docker-tools +tomesd +iso4217 +stamina +sqltrie +pem +localstack-client +django-webpack-loader +aiodocker +chargebee +mkdocs-meta-manager +types-aioboto3 +feu +mypy-boto3-application-autoscaling +cmakelang +iterfzf +sly +compressed-rtf +escapism +python-json-config +reportportal-client +lazify +lpips +flake8-junit-report-basic +pytest-archon +pyzbar +dvclive +torch-audiomentations +graphlib-backport +flask-oauthlib +flake8-broken-line +pytest-cases +xmlunittest +pebble +types-typing-extensions +pyqtgraph +dvc-http +nats-py +primepy +openvino-telemetry +lucopy +mkdocs-link-marker +jinja2-pluralize +types-aiobotocore-lambda +dapr +mariadb +pyjwkest +pysam +graspologic-native +testing-postgresql +google-reauth +array-api-compat +django-two-factor-auth +pytest-find-dependencies +uncalled +azure-containerregistry +roman +shellcheck-py +ruyaml +torch-pitch-shift +mkdocs-auto-tag-plugin +pandas-market-calendars +pyloudnorm +username +apache-airflow-providers-atlassian-jira +qudida +asyncio-atexit +mike +google-cloud-functions +snapshot-restore-py +django-auth-ldap +scooby +art +flake8-annotations +great-expectations-experimental +starrocks +workos +chdb +cron-converter +mf2py +bz2file +pyrdfa3 +dagster-cloud-cli +okta +mkdocs-glightbox +seqio-nightly +pytest-clarity +plotly-resampler +plux +tensorboard-plugin-profile +extruct +openvino +pyjsparser +googletrans +django-auditlog +pyfarmhash +json-stream +cvdupdate +types-appdirs +oslo-log +trafaret +copier +hurry-filesize +python-jsonschema-objects +pvlib +xpln2me +grpcio-testing +djangorestframework-dataclasses +nvidia-cuda-nvcc-cu12 +google-cloud-appengine-admin +pyannote-pipeline +python-logstash +django-ckeditor +nbstripout +mailjet-rest +opentelemetry-instrumentation-confluent-kafka +pyomo +django-object-actions +target-hotglue +pismosendlogs +opentelemetry-instrumentation-mysql +apache-airflow-providers-opsgenie +quadprog +pytest-freezer +django-modeltranslation +sqlalchemy-json +neptune-client +imapclient +expecttest +logging +flupy +pytest-wake +imutils +hyppo +langchain-postgres +gcloud-aio-pubsub +cement +elasticsearch8 +ascii-magic +lm-eval +currencyconverter +jinja2-ansible-filters +sphinxcontrib-apidoc +python-whois +apache-airflow-providers-apache-hive +pyminizip +types-bleach +fastcluster +docarray +databricks-labs-blueprint +opentelemetry-resourcedetector-process +azure-mgmt-managedservices +pytest-flask +authcaptureproxy +opentelemetry-container-distro +rangehttpserver +fastexcel +hnswlib +lkml +langid +sagemaker-mlflow +django-localflavor +rioxarray +pyvista +acryl-datahub-airflow-plugin +htmlmin2 +django-coverage-plugin +inline-snapshot +nutter +langchain-pinecone +sqlalchemy-databricks +ibis-framework +keplergl +browser-use +libusb1 +aws-cdk-asset-node-proxy-agent-v5 +jaraco-collections +python-string-utils +growthbook +azureml-pipeline-core +types-oauthlib +logfury +pytest-opentelemetry +mypy-boto3-efs +langgraph-checkpoint-postgres +browsergym-core +zipcodes +fortifyapi +udocker +gevent-websocket +apache-airflow-providers-samba +evdev +langchainhub +ydb-dbapi +google-cloud-ndb +grep-ast +pytest-datadir +mypy-boto3-sagemaker-runtime +spark-sklearn +rouge +duo-client +opentelemetry-instrumentation-psycopg +mujoco +appier +flask-testing +descartes +drf-extensions +pyvim +pyop +torchinfo +pytest-testmon +pyqrcode +openinference-instrumentation +opentelemetry-instrumentation-mysqlclient +opentelemetry-instrumentation-aio-pika +types-networkx +cbor +spacy-language-detection +mypy-boto3-textract +couchbase +sttable +yellowbrick +opentelemetry-instrumentation-falcon +jamo +mail-parser +sphinxext-rediraffe +faust-cchardet +apache-airflow-providers-apache-livy +zope-component +jupyter-highlight-selected-word +netmiko +p4python +flake8-debugger +hstspreload +jsonnet +whoosh +types-pycurl +libtmux +unstructured-inference +django-hijack +paddlepaddle +pip-check +ldapdomaindump +alembic-utils +google-apps-meet +jsonslicer +aiohttp-socks +python-benedict +textual-dev +wincertstore +spandrel-extra-arches +newrelic-telemetry-sdk +edgegrid-python +selectolax +tabcmd +tk +bumpversion +flaml +vobject +llama-index-readers-web +pyinstaller-versionfile +nutree +html-tag-names +flake8-commas +html-void-elements +wmill +qiskit +odxtools +unsloth-zoo +pyxirr +awsiotsdk +textual-serve +aws-lambda-typing +anyscale +zxcvbn +onnxruntime-genai +sqlite-utils +recordclass +gto +pymc +codefind +dagster-gcp +asyncclick +xmodem +pygerduty +uhashring +ajsonrpc +psycopg-c +anyconfig +flask-script +pyspellchecker +tsdownsample +infi-systray +pyvisa +types-fpdf2 +mapclassify +pymatgen +schemdraw +python-etcd +plantuml-markdown +webexteamssdk +pyiotools +pymiscutils +bugsnag +mypy-boto3-config +opentelemetry-instrumentation-pyramid +maybe-else +dagster-celery +pyheif +rdt +opentelemetry-instrumentation-aiopg +jupyter-nbextensions-configurator +darkdetect +csvw +coola +pysubtypes +rstcheck-core +junit2html +azureml-train-core +prettierfier +forex-python +pydotplus +pathmagic +django-guardian +mypy-boto3-eks +fastapi-slim +tree-sitter-typescript +pytensor +types-aiobotocore-ec2 +aws-error-utils +jurigged +mozilla-django-oidc +python-swiftclient +suds +xlsx2csv +ovld +codemagic-cli-tools +prefect-kubernetes +bayesian-optimization +paddleocr +segments +pytest-timeouts +types-python-jose +sodapy +opentelemetry-propagator-b3 +delighted +pyrepl +dbt-clickhouse +torch-fidelity +crhelper +gherkin-official +sphinx-togglebutton +libhoney +git-python +pylink-square +nmslib +opentelemetry-instrumentation-pymemcache +mypy-boto3-cloudfront +blackduck +pulp-glue +localstack-ext +sk-dist +polars-lts-cpu +mimesis +opentelemetry-instrumentation-click +fastai +pykerberos +alibabacloud-tea-openapi +pygraphviz +types-babel +google-cloud-org-policy +functools32 +ntc-templates +sphinx-notfound-page +ropwr +img2pdf +google-cloud-os-config +hidapi +apeye-core +dlinfo +petl +python-novaclient +tempora +streamlit-aggrid +iterators +py-moneyed +warcio +waiting +disposable-email-domains +python-interface +businesstimedelta +psycogreen +aerospike +datadog-logger +kafka +jsonalias +graphrag +mpi4py +throttlex +django-nested-admin +tsx +dapr-ext-fastapi +schematics +rlpycairo +google-cloud-asset +seqeval +streamerate +cantools +pytest-flakefinder +controlnet-aux +pydevd-pycharm +redlock-py +progressbar +slugify +sparkmeasure +pyuwsgi +pyawscron +types-aiobotocore-rds +backports-abc +pyvalid +prefixed +mypy-boto3-bedrock +mypy-boto3-kafka +python-matter-server +covdefaults +names +mypy-boto3-scheduler +python-monkey-business +sparkorm +anndata +google-cloud-access-context-manager +ping3 +pystac +httpie +sklearn2pmml +ezdxf +feast +textstat +cma +prov +pydevd +kedro-datasets +clipboard +scikit-plot +ast-grep-py +sanic-ext +mypy-boto3-codebuild +mypy-boto3-sso +jsonschema-rs +homeassistant +opentelemetry-test-utils +allure-behave +globus-sdk +oslo-context +backports-entry-points-selectable +gitlint-core +django-mysql +gitlint +selinux +home-assistant-chip-clusters +pysqlite3 +docstring-parser-fork +livekit-protocol +azureml-train-restclients-hyperdrive +mkdocs-techdocs-core +rpy2 +databricks-vectorsearch +intel-openmp +kagglehub +types-aiobotocore-cloudformation +tslearn +fnllm +line-bot-sdk +elasticsearch7 +numpy-quaternion +livekit-api +docling +jupyter-server-proxy +validator-collection +simpleitk +urlextract +traits +crowdstrike-falconpy +python3-xlib +java-manifest +embreex +pythran-openblas +openapi-python-client +importlab +robotframework-jsonlibrary +robotframework-browser +yandex-query-client +hypothesis-jsonschema +opentelemetry-instrumentation-remoulade +meteostat +catkin-pkg +opentelemetry-instrumentation-cassandra +fastapi-sso +googlesearch-python +dashscope +compress-pickle +apispec-oneofschema +fastapi-mail +pyapacheatlas +ordereddict +apeye +jsonseq +gcloud-rest-auth +torchlibrosa +pynetbox +pulp-cli +oauth2 +mypy-boto3-dms +cloudwatch +pysqlite3-binary +mypy-boto3-pricing +mypy-boto3-cloudtrail +flake8-variables-names +pyang +mkdocs-minify-plugin +unstructured-pytesseract +effdet +datarobot +alibabacloud-tea +types-defusedxml +robotframework-robocop +pytest-mypy +certbot +screeninfo +apache-airflow-providers-github +django-fsm +pythainlp +xlwings +enlighten +mypy-boto3-iot +django-constance +flask-dapr +roboflow +django-rq +yagmail +django-user-agents +pylint-gitlab +maison +pysimdjson +airflow-code-editor +sphinx-reredirects +ci-info +tfx-bsl +apache-airflow-providers-apache-druid +mypy-boto3-iot-data +correctionlib +coincurve +sphinx-click +customtkinter +sphinx-gallery +s2sphere +etelemetry +flake8-tidy-imports +julius +pyobjc-core +ncclient +bigframes +llama-index-readers-wikipedia +logfire +persistent +segno +clearml +csscompressor +pytd +zha-quirks +pyttsx3 +aiven-client +astpretty +pandarallel +lazy +swig +azureml-automl-core +bravado +docx +anywidget +colour-science +cherrypy +btrees +libusb-package +sklearn-crfsuite +rule-engine +testrail-api +mmhash3 +meshio +awscli-local +apify-client +sphinx-prompt +dockerfile +openshift +cdk8s +python-graphql-client +pytest-pudb +django-htmx +pyu2f +telebot +types-pysftp +mypy-boto3-codepipeline +mypy-boto3-organizations +mux-python +zope-i18n +types-factory-boy +tdqm +home-assistant-chip-core +amplitude-analytics +llama-index-tools-wikipedia +spotinst-agent +tika +mpire +svix +deepgram-sdk +mypy-boto3-wafv2 +solana +transliterate +robotframework-assertion-engine +pytype +schemathesis +morefs +mypy-boto3-resourcegroupstaggingapi +chalice +opentelemetry-exporter-jaeger +pykakasi +testresources +esprima +django-deprecate-fields +textual-imageview +aqtp +mypy-boto3-quicksight +bleak +mypy-boto3-elasticache +zigpy-znp +interpret-core +tobiko-cloud-helpers +objprint +opentelemetry-instrumentation-aiokafka +scantree +h3-pyspark +mypy-boto3-identitystore +databricks-labs-lsql +zigpy-deconz +mypy-boto3-apigatewaymanagementapi +getdaft +pyhmmer +zigpy-xbee +kaggle +alibabacloud-credentials +sqlite-fts4 +pycrdt +mypy-boto3-codedeploy +telnetlib3 +dramatiq +livekit +py-sr25519-bindings +bellows +azureml-train-automl-client +gptcache +mypy-boto3-apigatewayv2 +alibabacloud-tea-util +inscriptis +mypy-boto3-transcribe +boost-histogram +python-version +mypy-boto3-ce +pyiso8583 +pandas-profiling +pytest-alembic +lazy-model +libtpu +dirhash +labelbox +haystack-ai +cursor +mlserver +replicate +transforms3d +alibabacloud-openapi-util +plotly-express +dynamo-pandas +djoser +apache-airflow-providers-trino +mypy-boto3-servicediscovery +fusepy +google-api +az-cli +litestar +mpld3 +detect-delimiter +python-liquid +pytest-reportportal +azureml-pipeline-steps +cloudinary +pycollada +sphinx-bootstrap-theme +voyageai +scalecodec +django-json-widget +routes +pyinotify +multiaddr +assemblyai +langchain-mistralai +pipupgrade +utm +pyxero +pdqhash +mxnet-mkl +mypy-boto3-timestream-query +mypy-boto3-appconfig +aiosmtpd +mypy-boto3-redshift +alibabacloud-gateway-spi +yamlfix +sagemaker-data-insights +mypy-boto3-es +aim +django-tinymce +autodocsumm +cyclonedx-bom +aiortc +entrypoint2 +pymonetdb +quart-cors +flpc +grequests +multiprocessing-logging +mypy-boto3-dynamodbstreams +django-linear-migrations +django-multiselectfield +nipype +sql-formatter +email-reply-parser +bindep +keras-tuner +amundsen-common +mypy-boto3-neptunedata +mypy-boto3-translate +capsolver +statistics +mkdocs-mermaid2-plugin +robocorp-log +alibabacloud-endpoint-util +beanie +python-dynamodb-lock +alibabacloud-tea-xml +google-i18n-address +nvidia-ml-py3 +jax-cuda12-plugin +prospector +fds-sdk-utils +mergepythonclient +mypy-boto3-pinpoint +colorzero +gpiozero +pylibmc +returns +ip2location +pandas-datareader +mypy-boto3-sesv2 +apispec-webframeworks +pydomo +construct-typing +mygeotab +pywebpush +mypy-boto3-codeartifact +ada-url +dagster-snowflake +ase +hass-web-proxy-lib +stable-baselines3 +path-py +sccache +crochet +mypy-boto3-rds-data +fastapi-cache2 +simple-azure-blob-downloader +lizard +django-select2 +geojson-pydantic +pyspark-pandas +autogluon-core +jupyter-kernel-gateway +django-migration-linter +django-cleanup +simpervisor +azure-schemaregistry-avroencoder +ipaddr +django-rest-swagger +apache-airflow-providers-sendgrid +pyarmor +flask-swagger-ui +pyhdb +cron-schedule-triggers +dissect-target +fastembed +aiogoogle +lazy-imports +imap-tools +tox-ansible +lakefs-client +lcov-cobertura +salesforce-fuelsdk-sans +python-tds +twofish +tmtools +mnemonic +zope-i18nmessageid +fs-s3fs +mypy-boto3-ram +open-webui +llguidance +mypy-boto3-servicecatalog +mkl +pulp-cli-deb +mypy-boto3-location +splink +jax-cuda12-pjrt +pyxnat +py-grpc-prometheus +cdktf +pulp-glue-deb +llama-cpp-python +mypy-boto3-marketplace-entitlement +py-vapid +mypy-boto3-sso-oidc +coredis +coremltools +airflow-provider-lakefs +segtok +faiss-gpu +pockets +mypy-boto3-comprehend +mypy-boto3-securityhub +mypy-boto3-support +cmudict +starlark-pyo3 +stackprinter +sgp4 +mypy-boto3-mediaconvert +idf-component-manager +mypy-boto3-s3control +pyqtwebengine +qiskit-aer +e2b-code-interpreter +mypy-boto3-meteringmarketplace +mypy-boto3-service-quotas +mypy-boto3-synthetics +granian +clize +delocate +dbx +typos +pyprctl +pantab +rocketchat-api +prettyprinter +spaces +mypy-boto3-elb +sorl-thumbnail +dateformat +oauth2-client +hypothesis-graphql +mypy-boto3-mwaa +pydoe +mypy-boto3-route53resolver +parsl +mypy-boto3-guardduty +bpyutils +certvalidator +ffmpeg +trie +azureml-pipeline +meraki +randomname +pycobertura +pypd +mypy-boto3-connect +mypy-boto3-workspaces +valkey +mypy-boto3-amplify +gurobipy +jupyter-contrib-nbextensions +httpstan +dodgy +mypy-boto3-rekognition +stanza +mypy-boto3-sso-admin +fcache +neotime +empy +mypy-boto3-directconnect +portend +ua-parser-rs +mypy-boto3-acm-pca +dagster-pyspark +opentelemetry-instrumentation-langchain +yara-python +kaldiio +pyatlan +runez +apify-shared +mypy-boto3-cloudsearch +cut-cross-entropy +purecloudplatformclientv2 +yolo +od +einx +elasticsearch-curator +pyobjc-framework-cocoa +databind +mypy-boto3-cloudsearchdomain +pycaret +mypy-boto3-budgets +mypy-boto3-docdb +mypy-boto3-polly +auditwheel +py-bip39-bindings +django-money +clu +pyathenajdbc +mypy-boto3-license-manager +mypy-boto3-shield +mypy-boto3-storagegateway +mypy-boto3-appmesh +mypy-boto3-ec2-instance-connect +fastwarc +mypy-boto3-appsync +types-passlib +visitor +mypy-boto3-resource-groups +formencode +sliceline +mypy-boto3-mediaconnect +mypy-boto3-medialive +djangorestframework-jwt +mypy-boto3-kinesis-video-media +mypy-boto3-swf +mypy-boto3-waf +fds-sdk-paengine +mypy-boto3-machinelearning +mypy-boto3-lightsail +mypy-boto3-neptune +spark-expectations +case-conversion +ptvsd +mypy-boto3-lex-models +mypy-boto3-sdb +fds-sdk-sparengine +einops-exts +fds-protobuf-stach-extensions +mypy-boto3-serverlessrepo +mypy-boto3-kinesisvideo +mypy-boto3-kendra +sagemaker-datawrangler +varint +sphinxcontrib-napoleon +mypy-boto3-accessanalyzer +mypy-boto3-comprehendmedical +mypy-boto3-workmail +fds-protobuf-stach-v2 +batchgenerators +types-aiobotocore-sns +mypy-boto3-kinesis-video-archived-media +fds-protobuf-stach +mypy-boto3-fsx +mypy-boto3-iotsecuretunneling +mypy-boto3-workdocs +mypy-boto3-lex-runtime +mypy-boto3-outposts +bzt +mypy-boto3-snowball +mypy-boto3-backup +mypy-boto3-pi +mypy-boto3-waf-regional +autologging +mypy-boto3-marketplace-catalog +mypy-boto3-workmailmessageflow +ag2 +mypy-boto3-route53domains +mypy-boto3-timestream-write +mypy-boto3-qldb-session +mypy-boto3-sagemaker-a2i-runtime +mypy-boto3-kinesisanalytics +mypy-boto3-kinesisanalyticsv2 +mypy-boto3-codecommit +stestr +mypy-boto3-chime +mypy-boto3-pinpoint-email +mypy-boto3-mediatailor +mypy-boto3-personalize +pytest-explicit +simplefix +mypy-boto3-robomaker +basistheory +mypy-boto3-kinesis-video-signaling +mypy-boto3-mq +mypy-boto3-opsworks +pylint-celery +pytest-flake8 +spacy-curated-transformers +mypy-boto3-appstream +mypy-boto3-managedblockchain +torch-model-archiver +mypy-boto3-sms +mypy-boto3-pinpoint-sms-voice +bridgecrew +mypy-boto3-mediapackage-vod +mypy-boto3-gamelift +mypy-boto3-mturk +mypy-boto3-iotevents +viztracer +dagster-datadog +dj-rest-auth +mypy-boto3-mediapackage +mypy-boto3-imagebuilder +braintrust-api +mypy-boto3-health +mypy-boto3-mediastore-data +mypy-boto3-marketplacecommerceanalytics +mypy-boto3-savingsplans +mypy-boto3-mediastore +mypy-boto3-networkmanager +mypy-boto3-opsworkscm +mypy-boto3-qldb +arcticdb +mypy-boto3-iotthingsgraph +uptime-kuma-api +mypy-boto3-fms +mypy-boto3-compute-optimizer +mypy-boto3-sms-voice +mypy-boto3-iotanalytics +mypy-boto3-groundstation +mypy-boto3-mgh +spider-client +mypy-boto3-glacier +django-webtest +types-google-cloud-ndb +mypy-boto3-datasync +mypy-boto3-importexport +mypy-boto3-personalize-events +methoddispatch +globus-compute-endpoint +reprint +mypy-boto3-personalize-runtime +mypy-boto3-inspector +mypy-boto3-globalaccelerator +mypy-boto3-cognito-sync +mypy-boto3-cur +autogluon-features +mypy-boto3-ds +mypy-boto3-migrationhub-config +mypy-boto3-forecast +mypy-boto3-application-insights +mypy-boto3-codestar-notifications +mypy-boto3-elasticbeanstalk +mypy-boto3-iotevents-data +apache-airflow-providers-apache-beam +tm1py +mypy-boto3-iot-jobs-data +mypy-boto3-greengrass +mypy-boto3-iotsitewise +jupyter-contrib-core +mypy-boto3-emr-containers +mcap +mypy-boto3-clouddirectory +msgpack-numpy +awsebcli +mypy-boto3-autoscaling-plans +aiorun +pylibsrtp +evervault-attestation-bindings +starlette-testclient +yaml-config +mypy-boto3-ivs-realtime +mypy-boto3-cloud9 +mypy-boto3-cloudhsmv2 +django-configurations +tgcrypto +kedro-telemetry +mypy-boto3-dax +mypy-boto3-codeguru-reviewer +prefect-shell +mypy-boto3-forecastquery +secure +docling-core +mypy-boto3-devicefarm +ast-grep-cli +azure-ai-contentsafety +pysnmpcrypto +mypy-boto3-datapipeline +mypy-boto3-connectparticipant +markdown-exec +mypy-boto3-frauddetector +django-recaptcha +usaddress-scourgify +python-mimeparse +mypy-boto3-codestar-connections +gin-config +amundsen-databuilder +mypy-boto3-detective +mypy-boto3-discovery +mypy-boto3-cloudhsm +mypy-boto3-codeguruprofiler +pip-install-test +sox +panns-inference +dag-factory +tempita +evervault +mypy-boto3-elastictranscoder +sqlean-py +draftjs-exporter +mypy-boto3-macie2 +patchy +meltano +ibm-secrets-manager-sdk +pyjson5 +mdxpy +pylcs +munkres +mypy-boto3-ecr-public +python-debian +pyicu-binary +ws4py +aiotask-context +fugashi +cpplint +dagster-shell +mypy-boto3-bedrock-agent-runtime +python-schema-registry-client +meross-iot +pyocd +pyreadline +sspilib +hachoir +mypy-boto3-network-firewall +jc +rapidocr-onnxruntime +assisted-service-client +mypy-boto3-ivs +arize +mypy-boto3-appconfigdata +mypy-boto3-braket +autovizwidget +pybreaker +dbt-athena +flask-sock +pysolr +types-zstd +dm-control +hdijupyterutils +pyarabic +discord +python-binance +inscribe +g2p-en +pydeprecate +pypandoc-binary +extensionclass +antithesis +coiled +globus-identity-mapping +donfig +zope-location +doc8 +tensorflow-decision-forests +requirements-detector +literalai +mypy-boto3-s3outposts +heapdict +lifetimes +mypy-boto3-sagemaker-featurestore-runtime +semantic-link-sempy +mypy-boto3-sagemaker-edge +canopen +autogluon-tabular +mypy-boto3-servicecatalog-appregistry +files-com +streamlit-keyup +times +mypy-boto3-auditmanager +gfpgan +mypy-boto3-amp +livekit-agents +slack +mypy-boto3-iotwireless +wiki-fetch +together +wagtail +mypy-boto3-wellarchitected +pyroscope-io +flet +mypy-boto3-elastic-inference +mypy-boto3-bedrock-agent +reverse-geocoder +bravado-core +mypy-boto3-lookoutvision +sphinxcontrib-plantuml +autoray +mypy-boto3-amplifybackend +azureml-sdk +toml-sort +mypy-boto3-databrew +google-cloud-profiler +equinox +flask-apispec +asammdf +airtable +mypy-boto3-healthlake +impacket +mypy-boto3-appintegrations +pyro-ppl +mypy-boto3-greengrassv2 +mypy-boto3-lexv2-models +icalevents +mypy-boto3-customer-profiles +fastdtw +pennylane-lightning +css-inline +mypy-boto3-iotdeviceadvisor +mypy-boto3-lexv2-runtime +mypy-boto3-connect-contact-lens +zope-security +mypy-boto3-devops-guru +pdm-pep517 +pydrive +django-dirtyfields +paypalrestsdk +mltable +mypy-boto3-iotfleethub +djangorestframework-camel-case +phonemizer +distance +mkdocs-include-markdown-plugin +graphql-query +arxiv +measurement +jplephem +syncer +aioice +autogluon +python-openid +pyreadstat +amundsen-rds +azureml-inference-server-http +drf-jwt +mypy-boto3-lookoutmetrics +globus-compute-sdk +websocket +aws-kinesis-agg +pwlf +mypy-boto3-lookoutequipment +jaro-winkler +marshmallow3-annotations +mypy-boto3-mgn +mypy-boto3-fis +rust-demangler +pymupdf4llm +rlbot +pykcs11 +esp-idf-kconfig +flake8-string-format +flake8-pytest-style +mypy-boto3-s3tables +bce-python-sdk +mypy-boto3-account +human-json +mypy-boto3-payment-cryptography +mypy-boto3-ssm-incidents +mypy-boto3-apprunner +acres +mypy-boto3-payment-cryptography-data +adbc-driver-manager +typed-argument-parser +mypy-boto3-finspace +mypy-boto3-finspace-data +wiremock +mypy-boto3-ssm-contacts +kt-legacy +mypy-boto3-proton +pyvisa-py +mypy-boto3-applicationcostprofiler +youtube-dl +pybars4 +botbuilder-schema +better-exceptions +mypy-boto3-grafana +gcloud-aio-datastore +awslimitchecker +pylogbeat +pandas-flavor +keras-nightly +bump-my-version +smartystreets-python-sdk +tls-client +prefect-ray +mypy-boto3-cloudcontrol +simplekml +rpmfile +tensorflow-model-optimization +prisma +mypy-boto3-route53-recovery-control-config +xmldiff +pythran +pyproject-flake8 +connected-components-3d +django-admin-list-filter-dropdown +mypy-boto3-memorydb +mypy-boto3-chime-sdk-messaging +hist +mypy-boto3-redshift-serverless +mypy-boto3-wisdom +mypy-boto3-route53-recovery-readiness +mypy-boto3-route53-recovery-cluster +mypy-boto3-chime-sdk-identity +django-autocomplete-light +docling-parse +pymap3d +stopit +records +mediapy +cmsis-pack-manager +mypy-boto3-inspector2 +python-jsonpath +mypy-boto3-snow-device-management +azure-eventhub-checkpointstoreblob-aio +mypy-boto3-kafkaconnect +mypy-boto3-voice-id +mypy-boto3-keyspaces +pytest-codspeed +cmake-format +mplcursors +unleashclient +versionfinder +nameof +anyjson +botframework-connector +zmq +mypy-boto3-marketplace-agreement +mypy-boto3-workspaces-web +localstack +mypy-boto3-pinpoint-sms-voice-v2 +requests-gssapi +mypy-boto3-verifiedpermissions +exrex +fireworks-ai +djangorestframework-xml +mypy-boto3-opensearchserverless +mypy-boto3-cleanrooms +streamlit-folium +flake8-rst-docstrings +mypy-boto3-panorama +mypy-boto3-chime-sdk-meetings +dbt-trino +gjson +mypy-boto3-amplifyuibuilder +tencentcloud-sdk-python +mypy-boto3-billingconductor +mypy-boto3-datazone +mypy-boto3-backup-gateway +mypy-boto3-migrationhubstrategy +mypy-boto3-drs +mypy-boto3-evidently +mypy-boto3-resiliencehub +django-log-request-id +arize-phoenix +mypy-boto3-iottwinmaker +mypy-boto3-iotfleetwise +mypy-boto3-omics +mypy-boto3-rum +mypy-boto3-migration-hub-refactor-spaces +mypy-boto3-workspaces-thin-client +mypy-boto3-connectcases +crontab +chess +mypy-boto3-chime-sdk-media-pipelines +mypy-boto3-chime-sdk-voice +mypy-boto3-ivschat +mypy-boto3-rbin +extra-streamlit-components +mypy-boto3-controltower +mypy-boto3-qbusiness +stream-inflate +mypy-boto3-arc-zonal-shift +mypy-boto3-support-app +mypy-boto3-vpc-lattice +gcloud-rest-datastore +mypy-boto3-b2bi +mypy-boto3-connectcampaigns +mypy-boto3-timestream-influxdb +mypy-boto3-m2 +pygam +mypy-boto3-ssm-sap +mypy-boto3-application-signals +mypy-boto3-cost-optimization-hub +luigi +mypy-boto3-codecatalyst +mypy-boto3-license-manager-user-subscriptions +etuples +mypy-boto3-cloudtrail-data +mypy-boto3-cleanroomsml +mypy-boto3-tnb +mypy-boto3-appfabric +mypy-boto3-osis +mypy-boto3-mediapackagev2 +lap +mypy-boto3-kendra-ranking +mypy-boto3-bcm-data-exports +eccodes +mypy-boto3-resource-explorer-2 +mypy-boto3-rolesanywhere +mypy-boto3-sagemaker-metrics +mypy-boto3-internetmonitor +plyfile +rejson +mypy-boto3-taxsettings +mypy-boto3-entityresolution +mypy-boto3-artifact +mypy-boto3-neptune-graph +mypy-boto3-codeguru-security +mypy-boto3-pipes +text-generation +mypy-boto3-trustedadvisor +mypy-boto3-supplychain +mypy-boto3-migrationhuborchestrator +mypy-boto3-cloudfront-keyvaluestore +cwcwidth +django-rest-polymorphic +mypy-boto3-chatbot +stream-unzip +mypy-boto3-controlcatalog +mypy-boto3-securitylake +mypy-boto3-privatenetworks +python-jwt +mypy-boto3-oam +mypy-boto3-docdb-elastic +apache-airflow-providers-apprise +mypy-boto3-apptest +mypy-boto3-qconnect +mypy-boto3-simspaceweaver +mypy-boto3-kinesis-video-webrtc-storage +adtk +py3langid +mypy-boto3-mailmanager +paramiko-expect +mypy-boto3-pca-connector-ad +mypy-boto3-freetier +mypy-boto3-medical-imaging +mypy-boto3-managedblockchain-query +mypy-boto3-codeconnections +mypy-boto3-launch-wizard +mypy-boto3-deadline diff --git a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py index c69de3bde..f580111ca 100644 --- a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py +++ b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py @@ -23,6 +23,7 @@ from macaron.malware_analyzer.pypi_heuristics.metadata.high_release_frequency import HighReleaseFrequencyAnalyzer from macaron.malware_analyzer.pypi_heuristics.metadata.one_release import OneReleaseAnalyzer from macaron.malware_analyzer.pypi_heuristics.metadata.source_code_repo import SourceCodeRepoAnalyzer +from macaron.malware_analyzer.pypi_heuristics.metadata.typosquatting_presence import TyposquattingPresenceAnalyzer from macaron.malware_analyzer.pypi_heuristics.metadata.unchanged_release import UnchangedReleaseAnalyzer from macaron.malware_analyzer.pypi_heuristics.metadata.wheel_absence import WheelAbsenceAnalyzer from macaron.malware_analyzer.pypi_heuristics.pypi_sourcecode_analyzer import PyPISourcecodeAnalyzer @@ -332,6 +333,7 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: SuspiciousSetupAnalyzer, WheelAbsenceAnalyzer, AnomalousVersionAnalyzer, + TyposquattingPresenceAnalyzer, ] # name used to query the result of all problog rules, so it can be accessed outside the model. @@ -395,12 +397,18 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: failed({Heuristics.ONE_RELEASE.value}), failed({Heuristics.ANOMALOUS_VERSION.value}). + % Package released with a name similar to a popular package. + {Confidence.MEDIUM.value}::trigger(malware_medium_confidence_3) :- + quickUndetailed, + failed({Heuristics.TYPOSQUATTING_PRESENCE.value}). + % ----- Evaluation ----- % Aggregate result {problog_result_access} :- trigger(malware_high_confidence_1). {problog_result_access} :- trigger(malware_high_confidence_2). {problog_result_access} :- trigger(malware_high_confidence_3). + {problog_result_access} :- trigger(malware_medium_confidence_3). {problog_result_access} :- trigger(malware_medium_confidence_2). {problog_result_access} :- trigger(malware_medium_confidence_1). query({problog_result_access}). From 428d5e9d07b1864d3afb3e6144aa33098ff105db Mon Sep 17 00:00:00 2001 From: Amine Date: Wed, 23 Apr 2025 09:54:31 +0100 Subject: [PATCH 2/5] feat: implement typosquatting detection for package names Adds a new security analysis feature to detect potential typosquatting in package names. Compares the package name against a list of popular packages using the Jaro-Winkler similarity algorithm. Packages exceeding a configurable threshold are flagged. Includes a default popular package list and an option for a custom list via configuration. Signed-off-by: Amine --- .../pypi_heuristics/metadata/typosquatting_presence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py index 71ea90988..f68899a4a 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py @@ -104,7 +104,7 @@ def substitution_func(self, char1: str, char2: str) -> float: ------- float 0.0 if the characters are the same, `self.keyboard` if they are - neighbors on a QWERTY keyboard, and `self.cost` otherwise. + neighbors on a QWERTY keyboard, otherwise `self.cost` . """ if char1 == char2: return 0.0 From 69a33bc6f945eb499bffff6501c5056f69539538 Mon Sep 17 00:00:00 2001 From: Amine Date: Wed, 23 Apr 2025 12:37:22 +0100 Subject: [PATCH 3/5] feat: implement typosquatting detection for package names Adds a new security analysis feature to detect potential typosquatting in package names. Compares the package name against a list of popular packages using the Jaro-Winkler similarity algorithm. Packages exceeding a configurable threshold are flagged. Includes a default popular package list and an option for a custom list via configuration. Signed-off-by: Amine --- .../metadata/typosquatting_presence.py | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py index f68899a4a..099798ba3 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py @@ -27,43 +27,43 @@ def __init__(self) -> None: self.scaling = 0.15 self.keyboard = 0.8 self.keyboard_layout = { - "1": (-1, 0), - "2": (-1, 1), - "3": (-1, 2), - "4": (-1, 3), - "5": (-1, 4), - "6": (-1, 5), - "7": (-1, 6), - "8": (-1, 7), - "9": (-1, 8), - "0": (-1, 9), - "-": (-1, 10), - "q": (0, 0), - "w": (0, 1), - "e": (0, 2), - "r": (0, 3), - "t": (0, 4), - "y": (0, 5), - "u": (0, 6), - "i": (0, 7), - "o": (0, 8), - "p": (0, 9), - "a": (1, 0), - "s": (1, 1), - "d": (1, 2), - "f": (1, 3), - "g": (1, 4), - "h": (1, 5), - "j": (1, 6), - "k": (1, 7), - "l": (1, 8), - "z": (2, 0), - "x": (2, 1), - "c": (2, 2), - "v": (2, 3), - "b": (2, 4), - "n": (2, 5), - "m": (2, 6), + "1": (0, 0), + "2": (0, 1), + "3": (0, 2), + "4": (0, 3), + "5": (0, 4), + "6": (0, 5), + "7": (0, 6), + "8": (0, 7), + "9": (0, 8), + "0": (0, 9), + "-": (0, 10), + "q": (1, 0), + "w": (1, 1), + "e": (1, 2), + "r": (1, 3), + "t": (1, 4), + "y": (1, 5), + "u": (1, 6), + "i": (1, 7), + "o": (1, 8), + "p": (1, 9), + "a": (2, 0), + "s": (2, 1), + "d": (2, 2), + "f": (2, 3), + "g": (2, 4), + "h": (2, 5), + "j": (2, 6), + "k": (2, 7), + "l": (2, 8), + "z": (3, 0), + "x": (3, 1), + "c": (3, 2), + "v": (3, 3), + "b": (3, 4), + "n": (3, 5), + "m": (3, 6), } if global_config.popular_packages_path is not None: From f14d71c0bd6d8c8f095a1df73f38985e3f67bf07 Mon Sep 17 00:00:00 2001 From: Amine Date: Tue, 29 Apr 2025 11:15:39 +0100 Subject: [PATCH 4/5] feat(typosquatting): add tests and move analyzer config to defaults.ini Added unit tests for typosquatting detection. Analyzer variables, including the file path, are now loaded from defaults.ini. Raised heuristic confidence level from medium to high. BREAKING CHANGE: Analyzer config must now be defined in defaults.ini. Signed-off-by: Amine --- src/macaron/config/defaults.ini | 11 ++ .../metadata/typosquatting_presence.py | 125 +++++++++++------- .../checks/detect_malicious_metadata_check.py | 7 +- .../pypi/test_typosquatting_presence.py | 78 +++++++++++ 4 files changed, 170 insertions(+), 51 deletions(-) create mode 100644 tests/malware_analyzer/pypi/test_typosquatting_presence.py diff --git a/src/macaron/config/defaults.ini b/src/macaron/config/defaults.ini index c46e09ce1..f831732ff 100644 --- a/src/macaron/config/defaults.ini +++ b/src/macaron/config/defaults.ini @@ -600,3 +600,14 @@ major_threshold = 20 epoch_threshold = 3 # The number of days +/- the day of publish the calendar versioning day may be. day_publish_error = 4 + +# The threshold ratio for two packages to be considered similar. +distance_ratio_threshold = 0.95 +# The Keyboard cost for two characters that are close to each other on the keyboard. +keyboard = 0.8 +# The scaling factor for the jaro winkler distance. +scaling = 0.15 +# The cost for two characters that are not close to each other on the keyboard. +cost = 1.0 +# The path to the file that contains the list of popular packages. +popular_packages_path = diff --git a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py index 099798ba3..c2af634b9 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py @@ -5,6 +5,7 @@ import logging import os +from macaron.config.defaults import defaults from macaron.config.global_config import global_config from macaron.json_tools import JsonType from macaron.malware_analyzer.pypi_heuristics.base_analyzer import BaseHeuristicAnalyzer @@ -17,58 +18,88 @@ class TyposquattingPresenceAnalyzer(BaseHeuristicAnalyzer): """Check whether the PyPI package has typosquatting presence.""" + KEYBOARD_LAYOUT = { + "1": (0, 0), + "2": (0, 1), + "3": (0, 2), + "4": (0, 3), + "5": (0, 4), + "6": (0, 5), + "7": (0, 6), + "8": (0, 7), + "9": (0, 8), + "0": (0, 9), + "-": (0, 10), + "q": (1, 0), + "w": (1, 1), + "e": (1, 2), + "r": (1, 3), + "t": (1, 4), + "y": (1, 5), + "u": (1, 6), + "i": (1, 7), + "o": (1, 8), + "p": (1, 9), + "a": (2, 0), + "s": (2, 1), + "d": (2, 2), + "f": (2, 3), + "g": (2, 4), + "h": (2, 5), + "j": (2, 6), + "k": (2, 7), + "l": (2, 8), + "z": (3, 0), + "x": (3, 1), + "c": (3, 2), + "v": (3, 3), + "b": (3, 4), + "n": (3, 5), + "m": (3, 6), + } + def __init__(self) -> None: super().__init__( name="typosquatting_presence_analyzer", heuristic=Heuristics.TYPOSQUATTING_PRESENCE, depends_on=None ) - self.popular_packages_path = os.path.join(global_config.resources_path, "popular_packages.txt") - self.distance_ratio_threshold = 0.95 - self.cost = 1 - self.scaling = 0.15 - self.keyboard = 0.8 - self.keyboard_layout = { - "1": (0, 0), - "2": (0, 1), - "3": (0, 2), - "4": (0, 3), - "5": (0, 4), - "6": (0, 5), - "7": (0, 6), - "8": (0, 7), - "9": (0, 8), - "0": (0, 9), - "-": (0, 10), - "q": (1, 0), - "w": (1, 1), - "e": (1, 2), - "r": (1, 3), - "t": (1, 4), - "y": (1, 5), - "u": (1, 6), - "i": (1, 7), - "o": (1, 8), - "p": (1, 9), - "a": (2, 0), - "s": (2, 1), - "d": (2, 2), - "f": (2, 3), - "g": (2, 4), - "h": (2, 5), - "j": (2, 6), - "k": (2, 7), - "l": (2, 8), - "z": (3, 0), - "x": (3, 1), - "c": (3, 2), - "v": (3, 3), - "b": (3, 4), - "n": (3, 5), - "m": (3, 6), - } + self.popular_packages_path, self.distance_ratio_threshold, self.keyboard, self.scaling, self.cost = ( + self._load_defaults() + ) if global_config.popular_packages_path is not None: self.popular_packages_path = global_config.popular_packages_path + def _load_defaults(self) -> tuple[str, float, float, float, float]: + """Load default settings from defaults.ini. + + Returns + ------- + tuple[str, float, float, float, float]: + The Major threshold, Epoch threshold, and Day published error. + """ + section_name = "heuristic.pypi" + default_path = os.path.join(global_config.resources_path, "popular_packages.txt") + if defaults.has_section(section_name): + section = defaults[section_name] + path = section.get("popular_packages_path", default_path) + # Fall back to default if the path in defaults.ini is empty + if not path.strip(): + path = default_path + return ( + path, + section.getfloat("distance_ratio_threshold", 0.95), + section.getfloat("keyboard", 0.8), + section.getfloat("scaling", 0.15), + section.getfloat("cost", 1.0), + ) + return ( + default_path, + 0.95, + 0.8, + 0.15, + 1.0, + ) + def are_neighbors(self, char1: str, char2: str) -> bool: """Check if two characters are adjacent on a QWERTY keyboard. @@ -84,8 +115,8 @@ def are_neighbors(self, char1: str, char2: str) -> bool: bool True if the characters are neighbors, False otherwise. """ - c1 = self.keyboard_layout.get(char1) - c2 = self.keyboard_layout.get(char2) + c1 = self.KEYBOARD_LAYOUT.get(char1) + c2 = self.KEYBOARD_LAYOUT.get(char2) if not c1 or not c2: return False return (abs(c1[0] - c2[0]) <= 1) and (abs(c1[1] - c2[1]) <= 1) @@ -213,7 +244,6 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes The result and related information collected during the analysis. """ # If there is a popular packages file, check if the package name is similar to any of them - package_name = pypi_package_json.component_name if not self.popular_packages_path or not os.path.exists(self.popular_packages_path): err_msg = f"Popular packages file not found or path not configured: {self.popular_packages_path}" logger.warning("%s. Skipping typosquatting check.", err_msg) @@ -228,6 +258,7 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes logger.error(err_msg) return HeuristicResult.SKIP, {"error": err_msg} + package_name = pypi_package_json.component_name for popular_package in popular_packages: if package_name == popular_package: return HeuristicResult.PASS, {"package_name": package_name} diff --git a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py index f580111ca..6df436a47 100644 --- a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py +++ b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py @@ -398,9 +398,8 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: failed({Heuristics.ANOMALOUS_VERSION.value}). % Package released with a name similar to a popular package. - {Confidence.MEDIUM.value}::trigger(malware_medium_confidence_3) :- - quickUndetailed, - failed({Heuristics.TYPOSQUATTING_PRESENCE.value}). + {Confidence.HIGH.value}::trigger(malware_high_confidence_4) :- + quickUndetailed, forceSetup, failed({Heuristics.TYPOSQUATTING_PRESENCE.value}). % ----- Evaluation ----- @@ -408,7 +407,7 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: {problog_result_access} :- trigger(malware_high_confidence_1). {problog_result_access} :- trigger(malware_high_confidence_2). {problog_result_access} :- trigger(malware_high_confidence_3). - {problog_result_access} :- trigger(malware_medium_confidence_3). + {problog_result_access} :- trigger(malware_high_confidence_4). {problog_result_access} :- trigger(malware_medium_confidence_2). {problog_result_access} :- trigger(malware_medium_confidence_1). query({problog_result_access}). diff --git a/tests/malware_analyzer/pypi/test_typosquatting_presence.py b/tests/malware_analyzer/pypi/test_typosquatting_presence.py new file mode 100644 index 000000000..204b328a2 --- /dev/null +++ b/tests/malware_analyzer/pypi/test_typosquatting_presence.py @@ -0,0 +1,78 @@ +# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. + +"""Tests for the TyposquattingPresenceAnalyzer heuristic.""" +# pylint: disable=redefined-outer-name + + +from pathlib import Path +from unittest.mock import MagicMock + +import pytest + +from macaron.malware_analyzer.pypi_heuristics.heuristics import HeuristicResult +from macaron.malware_analyzer.pypi_heuristics.metadata.typosquatting_presence import TyposquattingPresenceAnalyzer + + +@pytest.fixture() +def analyzer(tmp_path: Path) -> TyposquattingPresenceAnalyzer: + """Pytest fixture to create a TyposquattingPresenceAnalyzer instance with a dummy popular packages file.""" + # create a dummy popular packages file + pkg_file = tmp_path / "popular.txt" + pkg_file.write_text("\n".join(["requests", "flask", "pytest"])) + analyzer_instance = TyposquattingPresenceAnalyzer() + analyzer_instance.popular_packages_path = str(pkg_file) + return analyzer_instance + + +def test_analyze_exact_match_pass(analyzer: TyposquattingPresenceAnalyzer, pypi_package_json: MagicMock) -> None: + """Test the analyzer passes when the package name is an exact match to a popular package.""" + pypi_package_json.component_name = "requests" + result, info = analyzer.analyze(pypi_package_json) + assert result == HeuristicResult.PASS + assert info == {"package_name": "requests"} + + +def test_analyze_similar_name_fail(analyzer: TyposquattingPresenceAnalyzer, pypi_package_json: MagicMock) -> None: + """Test the analyzer fails when the package name is suspiciously similar to a popular package.""" + pypi_package_json.component_name = "reqursts" + result, info = analyzer.analyze(pypi_package_json) + assert result == HeuristicResult.FAIL + assert info["package_name"] == "reqursts" + assert info["popular_package"] == "requests" + # ratio should match or exceed threshold 0.95 + assert isinstance(info["similarity_ratio"], (int, float)) + assert info["similarity_ratio"] >= analyzer.distance_ratio_threshold + + +def test_analyze_unrelated_name_pass(analyzer: TyposquattingPresenceAnalyzer, pypi_package_json: MagicMock) -> None: + """Test the analyzer passes when the package name is not similar to any popular package.""" + pypi_package_json.component_name = "launchable" + result, info = analyzer.analyze(pypi_package_json) + assert result == HeuristicResult.PASS + assert info == {"package_name": "launchable"} + + +def test_analyze_nonexistent_file_skip(pypi_package_json: MagicMock) -> None: + """Test the analyzer skips if the popular packages file does not exist.""" + analyzer = TyposquattingPresenceAnalyzer() + analyzer.popular_packages_path = "/path/does/not/exist.txt" + result, info = analyzer.analyze(pypi_package_json) + assert result == HeuristicResult.SKIP + error_msg = info.get("error") + assert isinstance(error_msg, str) + assert "Popular packages file not found" in error_msg + + +@pytest.mark.parametrize( + ("s1", "s2", "expected"), + [ + ("requests", "requests", 1.0), + ("reqursts", "requests", 11 / 12), + ("abcd", "wxyz", 0.0), + ], +) +def test_jaro_distance(s1: str, s2: str, expected: float) -> None: + """Test the Jaro distance calculation.""" + analyzer = TyposquattingPresenceAnalyzer() + assert analyzer.jaro_distance(s1, s2) == expected From 80afd9ef9f0d839d3eccafa9729bb80b144830b9 Mon Sep 17 00:00:00 2001 From: Amine Date: Tue, 13 May 2025 10:20:25 +0100 Subject: [PATCH 5/5] chore(typosquatting): improve variable names and comments for clarity Signed-off-by: Amine --- .DS_Store | Bin 0 -> 6148 bytes src/macaron/__main__.py | 11 --- src/macaron/config/global_config.py | 5 -- .../pypi_heuristics/metadata/.DS_Store | Bin 0 -> 6148 bytes .../metadata/typosquatting_presence.py | 72 ++++++++++-------- .../checks/detect_malicious_metadata_check.py | 1 + tests/.DS_Store | Bin 0 -> 6148 bytes .../pypi/test_typosquatting_presence.py | 13 ++++ tests/parsers/.DS_Store | Bin 0 -> 6148 bytes tests/slsa_analyzer/.DS_Store | Bin 0 -> 8196 bytes 10 files changed, 53 insertions(+), 49 deletions(-) create mode 100644 .DS_Store create mode 100644 src/macaron/malware_analyzer/pypi_heuristics/metadata/.DS_Store create mode 100644 tests/.DS_Store create mode 100644 tests/parsers/.DS_Store create mode 100644 tests/slsa_analyzer/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..99617323d5172c778e09efceb4d25376027d1ef9 GIT binary patch literal 6148 zcmeHKJ5Iwu5SoZgXh1|MRIt~_utLNy+L4}F#L2S8{9-shugXOy z(ha|r0eN-_6*QwOx?b`88eduE`D9*A5TD*2zCZ1~z8o#*mR0ese{tD)H#X^x_UR0} z7c`}!wL3rVV)ZW(%V&5vxZgT?i)ZLWeV$}}RO>wajLlq}0cXG&_%{roW{afTiat66 z&VVz}Fd*ND02Ry(YsL8Kzz|yiU;=X#%%zu*oM4z4){5{zSW|(T%GP4Aro$dAE;FnZ zHJ#X+54N4zIuwq(WBm}ri8DnXodIW{&A^6U4y67cukZicNq*%FI0Ju*0Z#I9KEf?o yZSCBg)Y None: help="The directory where Macaron looks for already cloned repositories.", ) - main_parser.add_argument( - "-pp", - "--popular-packages-path", - required=False, - type=str, - default=None, - help="The path to the popular packages file used for typosquatting detection.", - dest="popular_packages_path", - ) - # Add sub parsers for each action. sub_parser = main_parser.add_subparsers(dest="action", help="Run macaron --help for help") @@ -589,7 +579,6 @@ def main(argv: list[str] | None = None) -> None: build_log_path=os.path.join(args.output_dir, "build_log"), debug_level=log_level, local_repos_path=args.local_repos_path, - popular_packages_path=args.popular_packages_path, resources_path=os.path.join(macaron.MACARON_PATH, "resources"), ) diff --git a/src/macaron/config/global_config.py b/src/macaron/config/global_config.py index 805e9e7fa..8befb4045 100644 --- a/src/macaron/config/global_config.py +++ b/src/macaron/config/global_config.py @@ -49,9 +49,6 @@ class GlobalConfig: #: The path to the local .m2 Maven repository. This attribute is None if there is no available .m2 directory. local_maven_repo: str | None = None - #: The path to the popular packages file. - popular_packages_path: str | None = None - def load( self, macaron_path: str, @@ -60,7 +57,6 @@ def load( debug_level: int, local_repos_path: str, resources_path: str, - popular_packages_path: str, ) -> None: """Initiate the GlobalConfig object. @@ -85,7 +81,6 @@ def load( self.debug_level = debug_level self.local_repos_path = local_repos_path self.resources_path = resources_path - self.popular_packages_path = popular_packages_path def load_expectation_files(self, exp_path: str) -> None: """ diff --git a/src/macaron/malware_analyzer/pypi_heuristics/metadata/.DS_Store b/src/macaron/malware_analyzer/pypi_heuristics/metadata/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 None: self._load_defaults() ) - if global_config.popular_packages_path is not None: - self.popular_packages_path = global_config.popular_packages_path - def _load_defaults(self) -> tuple[str, float, float, float, float]: """Load default settings from defaults.ini. Returns ------- tuple[str, float, float, float, float]: - The Major threshold, Epoch threshold, and Day published error. + The path to the popular packages file, distance ratio threshold, + keyboard awareness factor, scaling factor, and cost factor. """ section_name = "heuristic.pypi" default_path = os.path.join(global_config.resources_path, "popular_packages.txt") if defaults.has_section(section_name): section = defaults[section_name] path = section.get("popular_packages_path", default_path) - # Fall back to default if the path in defaults.ini is empty + # Fall back to default if the path in defaults.ini is empty. if not path.strip(): path = default_path return ( @@ -100,14 +98,14 @@ def _load_defaults(self) -> tuple[str, float, float, float, float]: 1.0, ) - def are_neighbors(self, char1: str, char2: str) -> bool: + def are_neighbors(self, first_char: str, second_char: str) -> bool: """Check if two characters are adjacent on a QWERTY keyboard. Parameters ---------- - char1 : str + first_char : str The first character. - char2 : str + second_char : str The second character. Returns @@ -115,20 +113,20 @@ def are_neighbors(self, char1: str, char2: str) -> bool: bool True if the characters are neighbors, False otherwise. """ - c1 = self.KEYBOARD_LAYOUT.get(char1) - c2 = self.KEYBOARD_LAYOUT.get(char2) - if not c1 or not c2: + coordinates1 = self.KEYBOARD_LAYOUT.get(first_char) + coordinates2 = self.KEYBOARD_LAYOUT.get(second_char) + if not coordinates1 or not coordinates2: return False - return (abs(c1[0] - c2[0]) <= 1) and (abs(c1[1] - c2[1]) <= 1) + return (abs(coordinates1[0] - coordinates2[0]) <= 1) and (abs(coordinates1[1] - coordinates2[1]) <= 1) - def substitution_func(self, char1: str, char2: str) -> float: + def substitution_func(self, first_char: str, second_char: str) -> float: """Calculate the substitution cost between two characters. Parameters ---------- - char1 : str + first_char : str The first character. - char2 : str + second_char : str The second character. Returns @@ -137,9 +135,9 @@ def substitution_func(self, char1: str, char2: str) -> float: 0.0 if the characters are the same, `self.keyboard` if they are neighbors on a QWERTY keyboard, otherwise `self.cost` . """ - if char1 == char2: + if first_char == second_char: return 0.0 - if self.keyboard and self.are_neighbors(char1, char2): + if self.keyboard and self.are_neighbors(first_char, second_char): return self.keyboard return self.cost @@ -161,21 +159,22 @@ def jaro_distance(self, package_name: str, popular_package_name: str) -> float: if package_name == popular_package_name: return 1.0 - len1, len2 = len(package_name), len(popular_package_name) - if len1 == 0 or len2 == 0: + package_name_len = len(package_name) + popular_package_name_len = len(popular_package_name) + if package_name_len == 0 or popular_package_name_len == 0: return 0.0 - match_distance = max(len1, len2) // 2 - 1 + match_distance = max(package_name_len, popular_package_name_len) // 2 - 1 - package_name_matches = [False] * len1 - popular_package_name_matches = [False] * len2 + package_name_matches = [False] * package_name_len + popular_package_name_matches = [False] * popular_package_name_len matches = 0 - transpositions = 0.0 # Now a float to handle partial costs + transpositions = 0.0 # a float to handle partial costs. - # Count matches - for i in range(len1): + # Count matches. + for i in range(package_name_len): start = max(0, i - match_distance) - end = min(i + match_distance + 1, len2) + end = min(i + match_distance + 1, popular_package_name_len) for j in range(start, end): if popular_package_name_matches[j]: continue @@ -188,9 +187,9 @@ def jaro_distance(self, package_name: str, popular_package_name: str) -> float: if matches == 0: return 0.0 - # Count transpositions with possible keyboard awareness + # Count transpositions with possible keyboard awareness. k = 0 - for i in range(len1): + for i in range(package_name_len): if package_name_matches[i]: while not popular_package_name_matches[k]: k += 1 @@ -198,9 +197,11 @@ def jaro_distance(self, package_name: str, popular_package_name: str) -> float: transpositions += self.substitution_func(package_name[i], popular_package_name[k]) k += 1 - transpositions /= 2.0 # Adjust for transpositions being counted twice + transpositions /= 2.0 # Adjust for transpositions being counted twice. - return (matches / len1 + matches / len2 + (matches - transpositions) / matches) / 3.0 + return ( + matches / package_name_len + matches / popular_package_name_len + (matches - transpositions) / matches + ) / 3.0 def ratio(self, package_name: str, popular_package_name: str) -> float: """Calculate the Jaro-Winkler distance ratio. @@ -243,7 +244,6 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes tuple[HeuristicResult, dict[str, JsonType]]: The result and related information collected during the analysis. """ - # If there is a popular packages file, check if the package name is similar to any of them if not self.popular_packages_path or not os.path.exists(self.popular_packages_path): err_msg = f"Popular packages file not found or path not configured: {self.popular_packages_path}" logger.warning("%s. Skipping typosquatting check.", err_msg) @@ -253,13 +253,19 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes try: with open(self.popular_packages_path, encoding="utf-8") as file: popular_packages = file.read().splitlines() - except OSError as e: - err_msg = f"Could not read popular packages file {self.popular_packages_path}: {e}" + except OSError as exception: + err_msg = f"Could not read popular packages file {self.popular_packages_path}: {exception}" logger.error(err_msg) return HeuristicResult.SKIP, {"error": err_msg} + if not popular_packages: + err_msg = f"Popular packages file is empty: {self.popular_packages_path}" + logger.warning(err_msg) + return HeuristicResult.SKIP, {"error": err_msg} + package_name = pypi_package_json.component_name for popular_package in popular_packages: + # If there is a popular packages file, check if the package name is similar to any of them. if package_name == popular_package: return HeuristicResult.PASS, {"package_name": package_name} diff --git a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py index 6df436a47..dba8c3763 100644 --- a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py +++ b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py @@ -408,6 +408,7 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: {problog_result_access} :- trigger(malware_high_confidence_2). {problog_result_access} :- trigger(malware_high_confidence_3). {problog_result_access} :- trigger(malware_high_confidence_4). + {problog_result_access} :- trigger(malware_high_confidence_5). {problog_result_access} :- trigger(malware_medium_confidence_2). {problog_result_access} :- trigger(malware_medium_confidence_1). query({problog_result_access}). diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ec72b3bf49c2b2264081e28eb4c117d776dd8851 GIT binary patch literal 6148 zcmeHK%}T>S5T30Ci+U({5l_LphrWQs67OoSN`GuoBsImhpq_H~349Wdo)q*k1RtPp z;5R#?CZ-id5GgY-^KEu#HZvb&Crd=GKJM0tszg+QGM1NMrUxTGuU zQIk$-Sn#$9W55{rZw&CayGm_3gT|EoeiO_!DWNfqXuvi;t~HL5{y<9D-1~lHKZ&AF zoOBRTxqduckDnj5e-hpOMfBh#n{^AX#bMS(wIgT;I;38{vW=~2KG_-$Zg1}QUwjRP zz52;VwqN<>^V;RS7Qu6y&N(Bt`K%^)MUQMw7fvT@QDfl literal 0 HcmV?d00001 diff --git a/tests/malware_analyzer/pypi/test_typosquatting_presence.py b/tests/malware_analyzer/pypi/test_typosquatting_presence.py index 204b328a2..8b757d7bc 100644 --- a/tests/malware_analyzer/pypi/test_typosquatting_presence.py +++ b/tests/malware_analyzer/pypi/test_typosquatting_presence.py @@ -76,3 +76,16 @@ def test_jaro_distance(s1: str, s2: str, expected: float) -> None: """Test the Jaro distance calculation.""" analyzer = TyposquattingPresenceAnalyzer() assert analyzer.jaro_distance(s1, s2) == expected + + +def test_empty_popular_packages_file(tmp_path: Path, pypi_package_json: MagicMock) -> None: + """Test the analyzer skips when the popular packages file is empty.""" + pkg_file = tmp_path / "empty_popular.txt" + pkg_file.write_text("") # Create an empty file + analyzer = TyposquattingPresenceAnalyzer() + analyzer.popular_packages_path = str(pkg_file) + result, info = analyzer.analyze(pypi_package_json) + assert result == HeuristicResult.SKIP + error_msg = info.get("error") + assert isinstance(error_msg, str) + assert "Popular packages file is empty" in error_msg diff --git a/tests/parsers/.DS_Store b/tests/parsers/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d13e152ba04f95bceb0faca9b50af387107cce44 GIT binary patch literal 6148 zcmeHKu};G<5IsYNss&Ug5R-qQ1AC~_m8D`sDoKkdQbMX02DYqx1{(_!I|~yF3p*Pl zU%@-uw6WS0SSp0>B>Oq%yZGg)_SHnB+E;@XQIm*jXpBJ}-2&rzZgaNgJe;h|JNgt; zpN4cwy}4*{hytR(UsHhhZUeiuM2yxmPHSK5j`4~SukDNFf1?WoN~GV@0i9k!I?Am86PsA%GfgJn~!)eeK(w!#Lrm& zB;M!!z`W~}fa?V3V)YtR)wmetn z^RhqXGtDh?STygi`^4*ttyTflY_?j%qV%GGC?E<<72xwhqcMgK3ybFHKxeK1zyNMz z7@Ham)a(F;4hxI$z?4k|+EnGP7|N!@uU%Z|u&`*;Ntu=L9=Eb`Hxy-7hhLj;QlUlZ zMFCO3S76;fHo5+Hs_*}Pl01n5qQJjWKm}1ZYU7h!ZJql#uC*HN1dWa33X3KMoh!#? ez@>N>%^2o14}hV=!Xi8{`6FOtkWLg>sREy_$erK- literal 0 HcmV?d00001 diff --git a/tests/slsa_analyzer/.DS_Store b/tests/slsa_analyzer/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5ebce8cc4fff0bcef387b652deb853fb8155a398 GIT binary patch literal 8196 zcmeI1!Ab)$5QZnUO0gitlZc1CDuVh3tJIT6AE4E?v|`&WtqR^2@$M`52tI`;LGUJm z2d^HztN$diCT?RDLXhsws=b9-w+p_?x z5KmO29n=;zsFLxP4pBf95Cud5Q9u;<3ku+y&7~0d?we6*MFCOZUn;=whX|EzY;9_+ zes!SHDFAF9-MZi!d4P_Itc|Trjn$!;(_|0IhANw4C=-tHh}~ghYg1!QI4KiO%HCPo z3`Oa?BQDbJq+(;G6$M0rtOA_77m=-3s7J?M{$6P}*S2d(r>BF|;3n+ZNy_EjcCrhP z!qZA=HoCi9|IEAo4ewsdrMO_2DSO6HqkS59l#};cAIDv`>REHX{h-;rIXcNZQOL*R zD5T6W4URo1H^6yEpN?={qlU*rr)MEQm+Sg!$Tf0Kmv5Y}$Em8!S)?Y|4n8GSGbdqB zm$kmwdebm4uS+}L+vCmY=Uc*F!$ z4PS_R-y7eF^Vf9_MC_Ly=o9Re26eHvZf3vaZl6PbF8|}rkbmTy?h_d2>v85w%sB